// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

/*XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
XX                                                                           XX
XX                          Compiler                                         XX
XX                                                                           XX
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
*/
#include "jitpch.h"
#ifdef _MSC_VER
#pragma hdrstop
#endif // _MSC_VER
#include "hostallocator.h"
#include "emit.h"
#include "ssabuilder.h"
#include "valuenum.h"
#include "rangecheck.h"
#include "lower.h"
#include "stacklevelsetter.h"
#include "patchpointinfo.h"
#include "fgprofilesynthesis.h"
#include "jitstd/algorithm.h"
#include "minipal/time.h"
#include "minipal/utf8.h"

extern ICorJitHost* g_jitHost;

unsigned Compiler::jitTotalMethodCompiled = 0;

#if defined(DEBUG)
LONG Compiler::jitNestingLevel = 0;
#endif // defined(DEBUG)

// static
bool                Compiler::s_pAltJitExcludeAssembliesListInitialized = false;
AssemblyNamesList2* Compiler::s_pAltJitExcludeAssembliesList            = nullptr;

#ifdef DEBUG
// static
bool                Compiler::s_pJitDisasmIncludeAssembliesListInitialized = false;
AssemblyNamesList2* Compiler::s_pJitDisasmIncludeAssembliesList            = nullptr;

// static
bool       Compiler::s_pJitFunctionFileInitialized = false;
MethodSet* Compiler::s_pJitMethodSet               = nullptr;
#endif // DEBUG

#ifdef CONFIGURABLE_ARM_ABI
// static
bool GlobalJitOptions::compFeatureHfa          = false;
LONG GlobalJitOptions::compUseSoftFPConfigured = 0;
#endif // CONFIGURABLE_ARM_ABI

/*****************************************************************************
 *
 *  Little helpers to grab the current cycle counter value; this is done
 *  differently based on target architecture, host toolchain, etc. The
 *  main thing is to keep the overhead absolutely minimal; in fact, on
 *  x86/x64 we use RDTSC even though it's not thread-safe; GetThreadCycles
 *  (which is monotonous) is just too expensive.
 */
#ifdef FEATURE_JIT_METHOD_PERF

#if defined(HOST_X86) || defined(HOST_AMD64)

#if defined(_MSC_VER)

#include <intrin.h>
inline bool _our_GetThreadCycles(uint64_t* cycleOut)
{
    *cycleOut = __rdtsc();
    return true;
}

#elif defined(__GNUC__)

inline bool _our_GetThreadCycles(uint64_t* cycleOut)
{
    uint32_t hi, lo;
    __asm__ __volatile__("rdtsc" : "=a"(lo), "=d"(hi));
    *cycleOut = (static_cast<uint64_t>(hi) << 32) | static_cast<uint64_t>(lo);
    return true;
}

#else // neither _MSC_VER nor __GNUC__

// The following *might* work - might as well try.
#define _our_GetThreadCycles(cp) GetThreadCycles(cp)

#endif

#elif defined(HOST_ARM) || defined(HOST_ARM64)

// If this doesn't work please see ../gc/gc.cpp for additional ARM
// info (and possible solutions).
#define _our_GetThreadCycles(cp) GetThreadCycles(cp)

#else // not x86/x64 and not ARM

// Don't know what this target is, but let's give it a try; if
// someone really wants to make this work, please add the right
// code here.
#define _our_GetThreadCycles(cp) GetThreadCycles(cp)

#endif // which host OS

const BYTE genTypeSizes[] = {
#define DEF_TP(tn, nm, jitType, sz, sze, asze, st, al, regTyp, regFld, csr, ctr, tf) sz,
#include "typelist.h"
#undef DEF_TP
};

const BYTE genTypeAlignments[] = {
#define DEF_TP(tn, nm, jitType, sz, sze, asze, st, al, regTyp, regFld, csr, ctr, tf) al,
#include "typelist.h"
#undef DEF_TP
};

const BYTE genTypeStSzs[] = {
#define DEF_TP(tn, nm, jitType, sz, sze, asze, st, al, regTyp, regFld, csr, ctr, tf) st,
#include "typelist.h"
#undef DEF_TP
};

const BYTE genActualTypes[] = {
#define DEF_TP(tn, nm, jitType, sz, sze, asze, st, al, regTyp, regFld, csr, ctr, tf) jitType,
#include "typelist.h"
#undef DEF_TP
};

#endif // FEATURE_JIT_METHOD_PERF

/*****************************************************************************/
#ifdef DEBUG
/*****************************************************************************/

static FILE* jitSrcFilePtr;

static unsigned jitCurSrcLine;

void Compiler::JitLogEE(unsigned level, const char* fmt, ...)
{
    va_list args;

    if (verbose)
    {
        va_start(args, fmt);
        vflogf(jitstdout(), fmt, args);
        va_end(args);
    }

    va_start(args, fmt);
    vlogf(level, fmt, args);
    va_end(args);
}

#endif // DEBUG

/*****************************************************************************/
#if defined(DEBUG) || MEASURE_NODE_SIZE || MEASURE_BLOCK_SIZE || DISPLAY_SIZES || CALL_ARG_STATS

static unsigned genMethodCnt;  // total number of methods JIT'ted
unsigned        genMethodICnt; // number of interruptible methods
unsigned        genMethodNCnt; // number of non-interruptible methods
static unsigned genSmallMethodsNeedingExtraMemoryCnt = 0;

#endif

/*****************************************************************************/
#if MEASURE_NODE_SIZE
NodeSizeStats genNodeSizeStats;
NodeSizeStats genNodeSizeStatsPerFunc;

unsigned  genTreeNcntHistBuckets[] = {10, 20, 30, 40, 50, 100, 200, 300, 400, 500, 1000, 5000, 10000, 0};
Histogram genTreeNcntHist(genTreeNcntHistBuckets);

unsigned  genTreeNsizHistBuckets[] = {1000, 5000, 10000, 50000, 100000, 500000, 1000000, 0};
Histogram genTreeNsizHist(genTreeNsizHistBuckets);
#endif // MEASURE_NODE_SIZE

/*****************************************************************************/
#if MEASURE_MEM_ALLOC

unsigned  memAllocHistBuckets[] = {64, 128, 192, 256, 512, 1024, 4096, 8192, 0};
Histogram memAllocHist(memAllocHistBuckets);
unsigned  memUsedHistBuckets[] = {16, 32, 64, 128, 192, 256, 512, 1024, 4096, 8192, 0};
Histogram memUsedHist(memUsedHistBuckets);

#endif // MEASURE_MEM_ALLOC

/*****************************************************************************
 *
 *  Variables to keep track of total code amounts.
 */

#if DISPLAY_SIZES

size_t grossVMsize; // Total IL code size
size_t grossNCsize; // Native code + data size
size_t totalNCsize; // Native code + data + GC info size (TODO-Cleanup: GC info size only accurate for JIT32_GCENCODER)
size_t gcHeaderISize; // GC header      size: interruptible methods
size_t gcPtrMapISize; // GC pointer map size: interruptible methods
size_t gcHeaderNSize; // GC header      size: non-interruptible methods
size_t gcPtrMapNSize; // GC pointer map size: non-interruptible methods

#endif // DISPLAY_SIZES

/*****************************************************************************
 *
 *  Variables to keep track of argument counts.
 */

#if CALL_ARG_STATS

unsigned argTotalCalls;
unsigned argHelperCalls;
unsigned argStaticCalls;
unsigned argNonVirtualCalls;
unsigned argVirtualCalls;

unsigned argTotalArgs; // total number of args for all calls (including objectPtr)
unsigned argTotalDWordArgs;
unsigned argTotalLongArgs;
unsigned argTotalFloatArgs;
unsigned argTotalDoubleArgs;

unsigned argTotalRegArgs;
unsigned argTotalTemps;
unsigned argTotalLclVar;
unsigned argTotalDeferred;
unsigned argTotalConst;

unsigned argTotalObjPtr;

unsigned argMaxTempsPerMethod;

unsigned  argCntBuckets[] = {0, 1, 2, 3, 4, 5, 6, 10, 0};
Histogram argCntTable(argCntBuckets);

unsigned  argDWordCntBuckets[] = {0, 1, 2, 3, 4, 5, 6, 10, 0};
Histogram argDWordCntTable(argDWordCntBuckets);

unsigned  argDWordLngCntBuckets[] = {0, 1, 2, 3, 4, 5, 6, 10, 0};
Histogram argDWordLngCntTable(argDWordLngCntBuckets);

unsigned  argTempsCntBuckets[] = {0, 1, 2, 3, 4, 5, 6, 10, 0};
Histogram argTempsCntTable(argTempsCntBuckets);

#endif // CALL_ARG_STATS

/*****************************************************************************
 *
 *  Variables to keep track of basic block counts.
 */

#if COUNT_BASIC_BLOCKS

//          --------------------------------------------------
//          Basic block count frequency table:
//          --------------------------------------------------
//              <=         1 ===>  26872 count ( 56% of total)
//               2 ..      2 ===>    669 count ( 58% of total)
//               3 ..      3 ===>   4687 count ( 68% of total)
//               4 ..      5 ===>   5101 count ( 78% of total)
//               6 ..     10 ===>   5575 count ( 90% of total)
//              11 ..     20 ===>   3028 count ( 97% of total)
//              21 ..     50 ===>   1108 count ( 99% of total)
//              51 ..    100 ===>    182 count ( 99% of total)
//             101 ..   1000 ===>     34 count (100% of total)
//            1001 ..  10000 ===>      0 count (100% of total)
//          --------------------------------------------------

unsigned  bbCntBuckets[] = {1, 2, 3, 5, 10, 20, 50, 100, 1000, 10000, 0};
Histogram bbCntTable(bbCntBuckets);

/* Histogram for the IL opcode size of methods with a single basic block */

unsigned  bbSizeBuckets[] = {1, 4, 8, 16, 32, 64, 128, 256, 512, 1024, 2048, 0};
Histogram bbOneBBSizeTable(bbSizeBuckets);

unsigned  computeReachabilitySetsIterationBuckets[] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 0};
Histogram computeReachabilitySetsIterationTable(computeReachabilitySetsIterationBuckets);

#endif // COUNT_BASIC_BLOCKS

Compiler::Compiler(ArenaAllocator*       arena,
                   CORINFO_METHOD_HANDLE methodHnd,
                   COMP_HANDLE           compHnd,
                   CORINFO_METHOD_INFO*  methodInfo,
                   InlineInfo*           inlineInfo)
    : compArenaAllocator(arena)
    , impInlineInfo(inlineInfo)
    , impPendingBlockMembers(CompAllocator(arena, CMK_Generic))
    , impSpillCliquePredMembers(CompAllocator(arena, CMK_Generic))
    , impSpillCliqueSuccMembers(CompAllocator(arena, CMK_Generic))
    , genIPmappings(CompAllocator(arena, CMK_DebugInfo))
    , genRichIPmappings(CompAllocator(arena, CMK_DebugInfo))
{
    info.compCompHnd    = compHnd;
    info.compMethodHnd  = methodHnd;
    info.compMethodInfo = methodInfo;
    info.compClassHnd   = compHnd->getMethodClass(methodHnd);

#ifdef DEBUG
    verbose = compIsForInlining() ? impInlineInfo->InlinerCompiler->verbose : false;
#endif

#if defined(DEBUG) || defined(LATE_DISASM) || DUMP_FLOWGRAPHS || DUMP_GC_TABLES
    info.compMethodName = eeGetMethodName(methodHnd);
    info.compClassName  = eeGetClassName(info.compClassHnd);
    info.compFullName   = eeGetMethodFullName(methodHnd);

    info.compMethodSuperPMIIndex = g_jitHost->getIntConfigValue("SuperPMIMethodContextNumber", -1);

    if (!compIsForInlining())
    {
        JitMetadata::report(this, JitMetadata::MethodFullName, info.compFullName, strlen(info.compFullName));
    }
#endif // defined(DEBUG) || defined(LATE_DISASM) || DUMP_FLOWGRAPHS

#ifdef DEBUG
    // Opt-in to jit stress based on method hash ranges.
    //
    // Note the default (with JitStressRange not set) is that all
    // methods will be subject to stress.
    static ConfigMethodRange fJitStressRange;
    fJitStressRange.EnsureInit(JitConfig.JitStressRange());
    assert(!fJitStressRange.Error());
    compAllowStress =
        fJitStressRange.Contains(info.compMethodHash()) &&
        (JitConfig.JitStressOnly().isEmpty() ||
         JitConfig.JitStressOnly().contains(info.compMethodHnd, info.compClassHnd, &info.compMethodInfo->args));

#endif // DEBUG

#if defined(FEATURE_EH_WINDOWS_X86)
    eeIsNativeAotAbi = IsTargetAbi(CORINFO_NATIVEAOT_ABI);
#endif

    if (compIsForInlining())
    {
        m_inlineStrategy = nullptr;
        compInlineResult = inlineInfo->inlineResult;
    }
    else
    {
        m_inlineStrategy = new (this, CMK_Inlining) InlineStrategy(this);
        compInlineResult = nullptr;
    }

    for (int i = 0; i < TYP_COUNT; i++)
    {
        fgBigOffsetMorphingTemps[i] = BAD_VAR_NUM;
    }

#ifdef DEBUG
    if (!compIsForInlining())
    {
        const int noStructPromotionValue = JitConfig.JitNoStructPromotion();
        assert(0 <= noStructPromotionValue && noStructPromotionValue <= 2);
        if (noStructPromotionValue == 1)
        {
            fgNoStructPromotion = true;
        }
        if (noStructPromotionValue == 2)
        {
            fgNoStructParamPromotion = true;
        }
    }
#endif // DEBUG

    structPromotionHelper = new (this, CMK_Promotion) StructPromotionHelper(this);

    if (!compIsForInlining())
    {
        codeGen = getCodeGenerator(this);
        hashBv::Init(this);

        //
        // Initialize all the per-method statistics gathering data structures.
        //
#if MEASURE_NODE_SIZE
        genNodeSizeStatsPerFunc.Init();
#endif // MEASURE_NODE_SIZE
    }

    for (MemoryKind memoryKind : allMemoryKinds())
    {
        m_memorySsaMap[memoryKind] = nullptr;
    }

#ifdef DEBUG
    if (!compIsForInlining())
    {
        compDoComponentUnitTestsOnce();
    }
#endif // DEBUG

    // check that HelperCallProperties are initialized
    assert(s_helperCallProperties.IsPure(CORINFO_HELP_GET_GCSTATIC_BASE));

    virtualStubParamInfo = new (this, CMK_Unknown) VirtualStubParamInfo(IsTargetAbi(CORINFO_NATIVEAOT_ABI));

    // compMatchedVM is set to true if both CPU/ABI and OS are matching the execution engine requirements
    //
    // Do we have a matched VM? Or are we "abusing" the VM to help us do JIT work (such as using an x86 native VM
    // with an ARM-targeting "altjit").
    // Match CPU/ABI for compMatchedVM
    info.compMatchedVM = info.compCompHnd->getExpectedTargetArchitecture() == CORINFO_ARCH_TARGET;

    // Match OS for compMatchedVM
    CORINFO_EE_INFO* eeInfo = eeGetEEInfo();

#ifdef TARGET_OS_RUNTIMEDETERMINED
    noway_assert(TargetOS::OSSettingConfigured);
#endif

    if (TargetOS::IsApplePlatform)
    {
        info.compMatchedVM = info.compMatchedVM && (eeInfo->osType == CORINFO_APPLE);
    }
    else if (TargetOS::IsUnix)
    {
        if (TargetArchitecture::IsX64)
        {
            // Apple x64 uses the Unix jit variant in crossgen2, not a special jit
            info.compMatchedVM =
                info.compMatchedVM && ((eeInfo->osType == CORINFO_UNIX) || (eeInfo->osType == CORINFO_APPLE));
        }
        else
        {
            info.compMatchedVM = info.compMatchedVM && (eeInfo->osType == CORINFO_UNIX);
        }
    }
    else if (TargetOS::IsWindows)
    {
        info.compMatchedVM = info.compMatchedVM && (eeInfo->osType == CORINFO_WINNT);
    }

    compMaxUncheckedOffsetForNullObject = eeInfo->maxUncheckedOffsetForNullObject;

    info.compProfilerCallback = false; // Assume false until we are told to hook this method.

    info.compCode         = methodInfo->ILCode;
    info.compILCodeSize   = methodInfo->ILCodeSize;
    info.compILImportSize = 0;

    info.compHasNextCallRetAddr    = false;
    info.compIsVarArgs             = false;
    info.compUsesAsyncContinuation = false;
}

//------------------------------------------------------------------------
// getJitGCType: Given the VM's CorInfoGCType convert it to the JIT's var_types
//
// Arguments:
//    gcType    - an enum value that originally came from an element
//                of the BYTE[] returned from getClassGClayout()
//
// Return Value:
//    The corresponding enum value from the JIT's var_types
//
// Notes:
//   The gcLayout of each field of a struct is returned from getClassGClayout()
//   as a BYTE[] but each BYTE element is actually a CorInfoGCType value
//   Note when we 'know' that there is only one element in this array
//   the JIT will often pass the address of a single BYTE, instead of a BYTE[]
//

var_types Compiler::getJitGCType(BYTE gcType)
{
    var_types     result      = TYP_UNKNOWN;
    CorInfoGCType corInfoType = (CorInfoGCType)gcType;

    if (corInfoType == TYPE_GC_NONE)
    {
        result = TYP_I_IMPL;
    }
    else if (corInfoType == TYPE_GC_REF)
    {
        result = TYP_REF;
    }
    else if (corInfoType == TYPE_GC_BYREF)
    {
        result = TYP_BYREF;
    }
    else
    {
        noway_assert(!"Bad value of 'gcType'");
    }
    return result;
}

#ifdef TARGET_X86
//---------------------------------------------------------------------------
// isTrivialPointerSizedStruct:
//    Check if the given struct type contains only one pointer-sized integer value type
//
// Arguments:
//    clsHnd - the handle for the struct type.
//
// Return Value:
//    true if the given struct type contains only one pointer-sized integer value type,
//    false otherwise.
//
bool Compiler::isTrivialPointerSizedStruct(CORINFO_CLASS_HANDLE clsHnd) const
{
    assert(info.compCompHnd->isValueClass(clsHnd));
    if (info.compCompHnd->getClassSize(clsHnd) != TARGET_POINTER_SIZE)
    {
        return false;
    }
    for (;;)
    {
        // all of class chain must be of value type and must have only one field
        if (!info.compCompHnd->isValueClass(clsHnd) || info.compCompHnd->getClassNumInstanceFields(clsHnd) != 1)
        {
            return false;
        }

        CORINFO_CLASS_HANDLE* pClsHnd   = &clsHnd;
        CORINFO_FIELD_HANDLE  fldHnd    = info.compCompHnd->getFieldInClass(clsHnd, 0);
        CorInfoType           fieldType = info.compCompHnd->getFieldType(fldHnd, pClsHnd);

        var_types vt = JITtype2varType(fieldType);

        if (fieldType == CORINFO_TYPE_VALUECLASS)
        {
            clsHnd = *pClsHnd;
        }
        else if (varTypeIsI(vt) && !varTypeIsGC(vt))
        {
            return true;
        }
        else
        {
            return false;
        }
    }
}
#endif // TARGET_X86

//---------------------------------------------------------------------------
// isNativePrimitiveStructType:
//    Check if the given struct type is an intrinsic type that should be treated as though
//    it is not a struct at the unmanaged ABI boundary.
//
// Arguments:
//    clsHnd - the handle for the struct type.
//
// Return Value:
//    true if the given struct type should be treated as a primitive for unmanaged calls,
//    false otherwise.
//
bool Compiler::isNativePrimitiveStructType(CORINFO_CLASS_HANDLE clsHnd)
{
    if (!isIntrinsicType(clsHnd))
    {
        return false;
    }
    const char* namespaceName = nullptr;
    const char* typeName      = getClassNameFromMetadata(clsHnd, &namespaceName);

    if (strcmp(namespaceName, "System.Runtime.InteropServices") != 0)
    {
        return false;
    }

    return strcmp(typeName, "CLong") == 0 || strcmp(typeName, "CULong") == 0 || strcmp(typeName, "NFloat") == 0;
}

//-----------------------------------------------------------------------------
// getPrimitiveTypeForStruct:
//     Get the "primitive" type that is used for a struct
//     of size 'structSize'.
//     We examine 'clsHnd' to check the GC layout of the struct and
//     return TYP_REF for structs that simply wrap an object.
//     If the struct is a one element HFA/HVA, we will return the
//     proper floating point or vector type.
//
// Arguments:
//    structSize - the size of the struct type, cannot be zero
//    clsHnd     - the handle for the struct type, used when may have
//                 an HFA or if we need the GC layout for an object ref.
//
// Return Value:
//    The primitive type (i.e. byte, short, int, long, ref, float, double)
//    used to pass or return structs of this size.
//    If we shouldn't use a "primitive" type then TYP_UNKNOWN is returned.
// Notes:
//    For 32-bit targets (X86/ARM32) the 64-bit TYP_LONG type is not
//    considered a primitive type by this method.
//    So a struct that wraps a 'long' is passed and returned in the
//    same way as any other 8-byte struct
//    For ARM32 if we have an HFA struct that wraps a 64-bit double
//    we will return TYP_DOUBLE.
//    For vector calling conventions, a vector is considered a "primitive"
//    type, as it is passed in a single register.
//
var_types Compiler::getPrimitiveTypeForStruct(unsigned structSize, CORINFO_CLASS_HANDLE clsHnd)
{
    assert(structSize != 0);

    var_types useType = TYP_UNKNOWN;

    // Start by determining if we have an HFA/HVA with a single element.
    if (GlobalJitOptions::compFeatureHfa)
    {
        switch (structSize)
        {
            case 4:
            case 8:
#ifdef TARGET_ARM64
            case 16:
#endif // TARGET_ARM64
            {
                var_types hfaType = GetHfaType(clsHnd);
                // We're only interested in the case where the struct size is equal to the size of the hfaType.
                if (varTypeIsValidHfaType(hfaType))
                {
                    if (genTypeSize(hfaType) == structSize)
                    {
                        useType = hfaType;
                    }
                    else
                    {
                        return TYP_UNKNOWN;
                    }
                }
            }
        }
        if (useType != TYP_UNKNOWN)
        {
            return useType;
        }
    }

    // Now deal with non-HFA/HVA structs.
    switch (structSize)
    {
        case 1:
            useType = TYP_UBYTE;
            break;

        case 2:
            useType = TYP_USHORT;
            break;

#if !defined(TARGET_XARCH) || defined(UNIX_AMD64_ABI)
        case 3:
            useType = TYP_INT;
            break;

#endif // !TARGET_XARCH || UNIX_AMD64_ABI

#ifdef TARGET_64BIT
        case 4:
            // We dealt with the one-float HFA above. All other 4-byte structs are handled as INT.
            useType = TYP_INT;
            break;

#if !defined(TARGET_XARCH) || defined(UNIX_AMD64_ABI)
        case 5:
        case 6:
        case 7:
            useType = TYP_I_IMPL;
            break;

#endif // !TARGET_XARCH || UNIX_AMD64_ABI
#endif // TARGET_64BIT

        case TARGET_POINTER_SIZE:
        {
            BYTE gcPtr = 0;
            // Check if this pointer-sized struct is wrapping a GC object
            info.compCompHnd->getClassGClayout(clsHnd, &gcPtr);
            useType = getJitGCType(gcPtr);
        }
        break;

        default:
            useType = TYP_UNKNOWN;
            break;
    }

    return useType;
}

//-----------------------------------------------------------------------------
// getReturnTypeForStruct:
//     Get the type that is used to return values of the given struct type.
//     If you have already retrieved the struct size then it should be
//     passed as the optional third argument, as this allows us to avoid
//     an extra call to getClassSize(clsHnd)
//
// Arguments:
//    clsHnd         - the handle for the struct type
//    callConv       - the calling convention of the function
//                     that returns this struct.
//    wbReturnStruct - An "out" argument with information about how
//                     the struct is to be returned
//    structSize     - the size of the struct type,
//                     or zero if we should call getClassSize(clsHnd)
//
// Return Value:
//    For wbReturnStruct you can pass a 'nullptr' and nothing will be written
//     or returned for that out parameter.
//    When *wbReturnStruct is SPK_PrimitiveType this method's return value
//       is the primitive type used to return the struct.
//    When *wbReturnStruct is SPK_ByReference this method's return value
//       is always TYP_UNKNOWN and the struct type is returned using a return buffer
//    When *wbReturnStruct is SPK_ByValue or SPK_ByValueAsHfa this method's return value
//       is always TYP_STRUCT and the struct type is returned using multiple registers.
//
// Assumptions:
//    The size must be the size of the given type.
//    The given class handle must be for a value type (struct).
//
// Notes:
//    About HFA types:
//        When the clsHnd is a one element HFA type then this method's return
//          value is the appropriate floating point primitive type and
//          *wbReturnStruct is SPK_PrimitiveType.
//        If there are two or more elements in the HFA type and the target supports
//          multireg return types then the return value is TYP_STRUCT and
//          *wbReturnStruct is SPK_ByValueAsHfa.
//        Additionally if there are two or more elements in the HFA type and
//          the target doesn't support multreg return types then it is treated
//          as if it wasn't an HFA type.
//    About returning TYP_STRUCT:
//        Whenever this method's return value is TYP_STRUCT it always means
//         that multiple registers are used to return this struct.
//
var_types Compiler::getReturnTypeForStruct(CORINFO_CLASS_HANDLE     clsHnd,
                                           CorInfoCallConvExtension callConv,
                                           structPassingKind*       wbReturnStruct /* = nullptr */,
                                           unsigned                 structSize /* = 0 */)
{
    var_types         useType             = TYP_UNKNOWN;
    structPassingKind howToReturnStruct   = SPK_Unknown; // We must change this before we return
    bool              canReturnInRegister = true;

    assert(clsHnd != NO_CLASS_HANDLE);

    if (structSize == 0)
    {
        structSize = info.compCompHnd->getClassSize(clsHnd);
    }
    assert(structSize > 0);

#ifdef SWIFT_SUPPORT
    if (callConv == CorInfoCallConvExtension::Swift)
    {
        const CORINFO_SWIFT_LOWERING* lowering = GetSwiftLowering(clsHnd);
        if (lowering->byReference)
        {
            howToReturnStruct = SPK_ByReference;
            useType           = TYP_UNKNOWN;
        }
        else if (lowering->numLoweredElements == 1)
        {
            useType = JITtype2varType(lowering->loweredElements[0]);
            if (genTypeSize(useType) == structSize)
            {
                howToReturnStruct = SPK_PrimitiveType;
            }
            else
            {
                howToReturnStruct = SPK_EnclosingType;
            }
        }
        else
        {
            howToReturnStruct = SPK_ByValue;
            useType           = TYP_STRUCT;
        }

        if (wbReturnStruct != nullptr)
        {
            *wbReturnStruct = howToReturnStruct;
        }

        return useType;
    }
#endif

#ifdef UNIX_AMD64_ABI
    // An 8-byte struct may need to be returned in a floating point register
    // So we always consult the struct "Classifier" routine
    //
    SYSTEMV_AMD64_CORINFO_STRUCT_REG_PASSING_DESCRIPTOR structDesc;
    eeGetSystemVAmd64PassStructInRegisterDescriptor(clsHnd, &structDesc);

    if (structDesc.eightByteCount == 1)
    {
        assert(structSize <= sizeof(double));
        assert(structDesc.passedInRegisters);

        if (structDesc.eightByteClassifications[0] == SystemVClassificationTypeSSE)
        {
            // If this is returned as a floating type, use that.
            // Otherwise, leave as TYP_UNKNOWN and we'll sort things out below.
            useType           = GetEightByteType(structDesc, 0);
            howToReturnStruct = SPK_PrimitiveType;
        }
    }
    else
    {
        // Return classification is not always size based...
        canReturnInRegister = structDesc.passedInRegisters;
        if (!canReturnInRegister)
        {
            assert(structDesc.eightByteCount == 0);
            howToReturnStruct = SPK_ByReference;
            useType           = TYP_UNKNOWN;
        }
    }
#elif UNIX_X86_ABI
    if (callConv != CorInfoCallConvExtension::Managed && !isNativePrimitiveStructType(clsHnd))
    {
        canReturnInRegister = false;
        howToReturnStruct   = SPK_ByReference;
        useType             = TYP_UNKNOWN;
    }
#elif defined(TARGET_RISCV64) || defined(TARGET_LOONGARCH64)
    if (structSize <= (TARGET_POINTER_SIZE * 2))
    {
        const CORINFO_FPSTRUCT_LOWERING* lowering = GetFpStructLowering(clsHnd);
        if (!lowering->byIntegerCallConv)
        {
            if (lowering->numLoweredElements == 1)
            {
                useType = JITtype2varType(lowering->loweredElements[0]);
                assert(varTypeIsFloating(useType));
                howToReturnStruct = SPK_PrimitiveType;
            }
            else
            {
                assert(lowering->numLoweredElements == 2);
                howToReturnStruct = SPK_ByValue;
                useType           = TYP_STRUCT;
            }
        }
    }

#endif
    if (TargetOS::IsWindows && !TargetArchitecture::IsArm32 && callConvIsInstanceMethodCallConv(callConv) &&
        !isNativePrimitiveStructType(clsHnd))
    {
        canReturnInRegister = false;
        howToReturnStruct   = SPK_ByReference;
        useType             = TYP_UNKNOWN;
    }

    // Check for cases where a small struct is returned in a register
    // via a primitive type.
    //
    // The largest "primitive type" is MAX_PASS_SINGLEREG_BYTES
    // so we can skip calling getPrimitiveTypeForStruct when we
    // have a struct that is larger than that.
    if (canReturnInRegister && (useType == TYP_UNKNOWN) && (structSize <= MAX_PASS_SINGLEREG_BYTES))
    {
        // We set the "primitive" useType based upon the structSize
        // and also examine the clsHnd to see if it is an HFA of count one
        //
        // The ABI for struct returns in varArg methods, is same as the normal case,
        // so pass false for isVararg
        useType = getPrimitiveTypeForStruct(structSize, clsHnd);

        if (useType != TYP_UNKNOWN)
        {
            if (structSize == genTypeSize(useType))
            {
                // Currently: 1, 2, 4, or 8 byte structs
                howToReturnStruct = SPK_PrimitiveType;
            }
            else
            {
                // Currently: 3, 5, 6, or 7 byte structs
                assert(structSize < genTypeSize(useType));
                howToReturnStruct = SPK_EnclosingType;
            }
        }
    }

#ifdef TARGET_64BIT
    // Note this handles an odd case when FEATURE_MULTIREG_RET is disabled and HFAs are enabled
    //
    // getPrimitiveTypeForStruct will return TYP_UNKNOWN for a struct that is an HFA of two floats
    // because when HFA are enabled, normally we would use two FP registers to pass or return it
    //
    // But if we don't have support for multiple register return types, we have to change this.
    // Since what we have is an 8-byte struct (float + float)  we change useType to TYP_I_IMPL
    // so that the struct is returned instead using an 8-byte integer register.
    //
    if ((FEATURE_MULTIREG_RET == 0) && (useType == TYP_UNKNOWN) && (structSize == (2 * sizeof(float))) && IsHfa(clsHnd))
    {
        useType           = TYP_I_IMPL;
        howToReturnStruct = SPK_PrimitiveType;
    }
#endif

    // Did we change this struct type into a simple "primitive" type?
    if (useType != TYP_UNKNOWN)
    {
        // If so, we should have already set howToReturnStruct, too.
        assert(howToReturnStruct != SPK_Unknown);
    }
    else if (canReturnInRegister) // We can't replace the struct with a "primitive" type
    {
        // See if we can return this struct by value, possibly in multiple registers
        // or if we should return it using a return buffer register
        //
        if ((FEATURE_MULTIREG_RET == 1) && (structSize <= MAX_RET_MULTIREG_BYTES))
        {
            // Structs that are HFA's are returned in multiple registers
            if (IsHfa(clsHnd))
            {
                // HFA's of count one should have been handled by getPrimitiveTypeForStruct
                assert(GetHfaCount(clsHnd) >= 2);

                // setup wbPassType and useType indicate that this is returned by value as an HFA
                //  using multiple registers
                howToReturnStruct = SPK_ByValueAsHfa;
                useType           = TYP_STRUCT;
            }
            else // Not an HFA struct type
            {

#ifdef UNIX_AMD64_ABI

                // The cases of (structDesc.eightByteCount == 1) and (structDesc.eightByteCount == 0)
                // should have already been handled
                assert(structDesc.eightByteCount > 1);
                // setup wbPassType and useType indicate that this is returned by value in multiple registers
                howToReturnStruct = SPK_ByValue;
                useType           = TYP_STRUCT;
                assert(structDesc.passedInRegisters == true);

#elif defined(TARGET_ARM64)

                // Structs that are pointer sized or smaller should have been handled by getPrimitiveTypeForStruct
                assert(structSize > TARGET_POINTER_SIZE);

                // On ARM64 structs that are 9-16 bytes are returned by value in multiple registers
                //
                if (structSize <= (TARGET_POINTER_SIZE * 2))
                {
                    // setup wbPassType and useType indicate that this is return by value in multiple registers
                    howToReturnStruct = SPK_ByValue;
                    useType           = TYP_STRUCT;
                }
                else // a structSize that is 17-32 bytes in size
                {
                    // Otherwise we return this struct using a return buffer
                    // setup wbPassType and useType indicate that this is returned using a return buffer register
                    //  (reference to a return buffer)
                    howToReturnStruct = SPK_ByReference;
                    useType           = TYP_UNKNOWN;
                }
#elif defined(TARGET_X86)

                // Only 8-byte structs are return in multiple registers.
                // We also only support multireg struct returns on x86 to match the native calling convention.
                // So return 8-byte structs only when the calling convention is a native calling convention.
                if (structSize == MAX_RET_MULTIREG_BYTES && callConv != CorInfoCallConvExtension::Managed)
                {
                    // setup wbPassType and useType indicate that this is return by value in multiple registers
                    howToReturnStruct = SPK_ByValue;
                    useType           = TYP_STRUCT;
                }
                else
                {
                    // Otherwise we return this struct using a return buffer
                    // setup wbPassType and useType indicate that this is returned using a return buffer register
                    //  (reference to a return buffer)
                    howToReturnStruct = SPK_ByReference;
                    useType           = TYP_UNKNOWN;
                }
#elif defined(TARGET_ARM)

                // Otherwise we return this struct using a return buffer
                // setup wbPassType and useType indicate that this is returned using a return buffer register
                //  (reference to a return buffer)
                howToReturnStruct = SPK_ByReference;
                useType           = TYP_UNKNOWN;

#elif defined(TARGET_LOONGARCH64) || defined(TARGET_RISCV64)

                // On LOONGARCH64/RISCV64 struct that is 1-16 bytes is returned by value in one/two register(s)
                howToReturnStruct = SPK_ByValue;
                useType           = TYP_STRUCT;

#else //  TARGET_XXX

                noway_assert(!"Unhandled TARGET in getReturnTypeForStruct (with FEATURE_MULTIREG_ARGS=1)");

#endif //  TARGET_XXX
            }
        }
        else // (structSize > MAX_RET_MULTIREG_BYTES) || (FEATURE_MULTIREG_RET == 0)
        {
            // We have a (large) struct that can't be replaced with a "primitive" type
            // and can't be returned in multiple registers

            // We return this struct using a return buffer register
            // setup wbPassType and useType indicate that this is returned using a return buffer register
            //  (reference to a return buffer)
            howToReturnStruct = SPK_ByReference;
            useType           = TYP_UNKNOWN;
        }
    }

    // 'howToReturnStruct' must be set to one of the valid values before we return
    assert(howToReturnStruct != SPK_Unknown);
    if (wbReturnStruct != nullptr)
    {
        *wbReturnStruct = howToReturnStruct;
    }

    return useType;
}

///////////////////////////////////////////////////////////////////////////////
//
// MEASURE_NOWAY: code to measure and rank dynamic occurrences of noway_assert.
// (Just the appearances of noway_assert, whether the assert is true or false.)
// This might help characterize the cost of noway_assert in non-DEBUG builds,
// or determine which noway_assert should be simple DEBUG-only asserts.
//
///////////////////////////////////////////////////////////////////////////////

#if MEASURE_NOWAY

struct FileLine
{
    char*    m_file;
    unsigned m_line;
    char*    m_condStr;

    FileLine()
        : m_file(nullptr)
        , m_line(0)
        , m_condStr(nullptr)
    {
    }

    FileLine(const char* file, unsigned line, const char* condStr)
        : m_line(line)
    {
        size_t newSize = (strlen(file) + 1) * sizeof(char);
        m_file         = HostAllocator::getHostAllocator().allocate<char>(newSize);
        strcpy_s(m_file, newSize, file);

        newSize   = (strlen(condStr) + 1) * sizeof(char);
        m_condStr = HostAllocator::getHostAllocator().allocate<char>(newSize);
        strcpy_s(m_condStr, newSize, condStr);
    }

    FileLine(const FileLine& other)
    {
        m_file    = other.m_file;
        m_line    = other.m_line;
        m_condStr = other.m_condStr;
    }

    // GetHashCode() and Equals() are needed by JitHashTable

    static unsigned GetHashCode(FileLine fl)
    {
        assert(fl.m_file != nullptr);
        unsigned code = fl.m_line;
        for (const char* p = fl.m_file; *p != '\0'; p++)
        {
            code += *p;
        }
        // Could also add condStr.
        return code;
    }

    static bool Equals(FileLine fl1, FileLine fl2)
    {
        return (fl1.m_line == fl2.m_line) && (0 == strcmp(fl1.m_file, fl2.m_file));
    }
};

typedef JitHashTable<FileLine, FileLine, size_t, HostAllocator> FileLineToCountMap;
FileLineToCountMap*                                             NowayAssertMap;

void Compiler::RecordNowayAssert(const char* filename, unsigned line, const char* condStr)
{
    if (NowayAssertMap == nullptr)
    {
        NowayAssertMap = new (HostAllocator::getHostAllocator()) FileLineToCountMap(HostAllocator::getHostAllocator());
    }
    FileLine fl(filename, line, condStr);
    size_t*  pCount = NowayAssertMap->LookupPointer(fl);
    if (pCount == nullptr)
    {
        NowayAssertMap->Set(fl, 1);
    }
    else
    {
        ++(*pCount);
    }
}

void RecordNowayAssertGlobal(const char* filename, unsigned line, const char* condStr)
{
    if ((JitConfig.JitMeasureNowayAssert() == 1) && (JitTls::GetCompiler() != nullptr))
    {
        JitTls::GetCompiler()->RecordNowayAssert(filename, line, condStr);
    }
}

struct NowayAssertCountMap
{
    size_t   count;
    FileLine fl;

    NowayAssertCountMap()
        : count(0)
    {
    }

    struct compare
    {
        bool operator()(const NowayAssertCountMap& elem1, const NowayAssertCountMap& elem2)
        {
            return (ssize_t)elem2.count < (ssize_t)elem1.count; // sort in descending order
        }
    };
};

void DisplayNowayAssertMap()
{
    if (NowayAssertMap != nullptr)
    {
        FILE* fout;

        const char* strJitMeasureNowayAssertFile = JitConfig.JitMeasureNowayAssertFile();
        if (strJitMeasureNowayAssertFile != nullptr)
        {
            fout = fopen_utf8(strJitMeasureNowayAssertFile, "a");
            if (fout == nullptr)
            {
                fprintf(jitstdout(), "Failed to open JitMeasureNowayAssertFile \"%s\"\n", strJitMeasureNowayAssertFile);
                return;
            }
        }
        else
        {
            fout = jitstdout();
        }

        // Iterate noway assert map, create sorted table by occurrence, dump it.
        unsigned             count = NowayAssertMap->GetCount();
        NowayAssertCountMap* nacp  = new NowayAssertCountMap[count];
        unsigned             i     = 0;

        for (FileLineToCountMap::Node* const iter : FileLineToCountMap::KeyValueIteration(NowayAssertMap))
        {
            nacp[i].count = iter->GetValue();
            nacp[i].fl    = iter->GetKey();
            ++i;
        }

        jitstd::sort(nacp, nacp + count, NowayAssertCountMap::compare());

        if (fout == jitstdout())
        {
            // Don't output the header if writing to a file, since we'll be appending to existing dumps in that case.
            fprintf(fout, "\nnoway_assert counts:\n");
            fprintf(fout, "count, file, line, text\n");
        }

        for (i = 0; i < count; i++)
        {
            fprintf(fout, "%zu, %s, %u, \"%s\"\n", nacp[i].count, nacp[i].fl.m_file, nacp[i].fl.m_line,
                    nacp[i].fl.m_condStr);
        }

        if (fout != jitstdout())
        {
            fclose(fout);
            fout = nullptr;
        }
    }
}

#endif // MEASURE_NOWAY

#if MEASURE_BLOCK_SIZE
size_t genFlowNodeSize;
size_t genFlowNodeCnt;
#endif // MEASURE_BLOCK_SIZE

/*****************************************************************************/
// We keep track of methods we've already compiled.

/*****************************************************************************
 *  Declare the statics
 */

#ifdef DEBUG
/* static */
LONG Compiler::s_compMethodsCount = 0; // to produce unique label names
#endif

#if MEASURE_MEM_ALLOC
/* static */
bool Compiler::s_dspMemStats = false;
#endif

#ifndef PROFILING_SUPPORTED
const bool Compiler::Options::compNoPInvokeInlineCB = false;
#endif

/*****************************************************************************
 *
 *  One time initialization code
 */

/* static */
void Compiler::compStartup()
{
#if DISPLAY_SIZES
    grossVMsize = grossNCsize = totalNCsize = 0;
#endif // DISPLAY_SIZES

    /* Initialize the table of tree node sizes */

    GenTree::InitNodeSize();

#ifdef JIT32_GCENCODER
    // Initialize the GC encoder lookup table

    GCInfo::gcInitEncoderLookupTable();
#endif

    /* Initialize the emitter */

    emitter::emitInit();

    // Static vars of ValueNumStore
    ValueNumStore::ValidateValueNumStoreStatics();

    compDisplayStaticSizes();
}

/*****************************************************************************
 *
 *  One time finalization code
 */

/* static */
void Compiler::compShutdown()
{
    if (s_pAltJitExcludeAssembliesList != nullptr)
    {
        s_pAltJitExcludeAssembliesList->~AssemblyNamesList2(); // call the destructor
        s_pAltJitExcludeAssembliesList = nullptr;
    }

#ifdef DEBUG
    if (s_pJitDisasmIncludeAssembliesList != nullptr)
    {
        s_pJitDisasmIncludeAssembliesList->~AssemblyNamesList2(); // call the destructor
        s_pJitDisasmIncludeAssembliesList = nullptr;
    }
#endif // DEBUG

#if MEASURE_NOWAY
    DisplayNowayAssertMap();
#endif // MEASURE_NOWAY

    /* Shut down the emitter */

    emitter::emitDone();

#if defined(DEBUG)
    // Finish reading and/or writing inline xml
    if (JitConfig.JitInlineDumpXmlFile() != nullptr)
    {
        FILE* file = fopen_utf8(JitConfig.JitInlineDumpXmlFile(), "a");
        if (file != nullptr)
        {
            InlineStrategy::FinalizeXml(file);
            fclose(file);
        }
        else
        {
            InlineStrategy::FinalizeXml();
        }
    }
#endif // defined(DEBUG)

#if defined(DEBUG) || MEASURE_NODE_SIZE || MEASURE_BLOCK_SIZE || DISPLAY_SIZES || CALL_ARG_STATS
    if (genMethodCnt == 0)
    {
        return;
    }
#endif

#if NODEBASH_STATS
    GenTree::ReportOperBashing(jitstdout());
#endif

#ifdef FEATURE_JIT_METHOD_PERF
    if (compJitTimeLogFilename != nullptr)
    {
        FILE* jitTimeLogFile = fopen_utf8(compJitTimeLogFilename, "a");
        if (jitTimeLogFile != nullptr)
        {
            CompTimeSummaryInfo::s_compTimeSummary.Print(jitTimeLogFile);
            fclose(jitTimeLogFile);
        }
    }

    JitTimer::Shutdown();
#endif // FEATURE_JIT_METHOD_PERF

#if COUNT_AST_OPERS

    // Add up all the counts so that we can show percentages of total
    unsigned totalCount = 0;
    for (unsigned op = 0; op < GT_COUNT; op++)
    {
        totalCount += GenTree::s_gtNodeCounts[op];
    }

    if (totalCount > 0)
    {
        struct OperInfo
        {
            unsigned   Count;
            unsigned   Size;
            genTreeOps Oper;
        };

        OperInfo opers[GT_COUNT];
        for (unsigned op = 0; op < GT_COUNT; op++)
        {
            opers[op] = {GenTree::s_gtNodeCounts[op], GenTree::s_gtTrueSizes[op], static_cast<genTreeOps>(op)};
        }

        jitstd::sort(opers, opers + ArrLen(opers), [](const OperInfo& l, const OperInfo& r) {
            // We'll be sorting in descending order.
            return l.Count >= r.Count;
        });

        unsigned remainingCount      = totalCount;
        unsigned remainingCountLarge = 0;
        unsigned remainingCountSmall = 0;

        unsigned countLarge = 0;
        unsigned countSmall = 0;

        jitprintf("\nGenTree operator counts (approximate):\n\n");

        for (OperInfo oper : opers)
        {
            unsigned size       = oper.Size;
            unsigned count      = oper.Count;
            double   percentage = 100.0 * count / totalCount;

            if (size > TREE_NODE_SZ_SMALL)
            {
                countLarge += count;
            }
            else
            {
                countSmall += count;
            }

            // Let's not show anything below a threshold
            if (percentage >= 0.5)
            {
                jitprintf("    GT_%-17s   %7u (%4.1lf%%) %3u bytes each\n", GenTree::OpName(oper.Oper), count,
                          percentage, size);
                remainingCount -= count;
            }
            else
            {
                if (size > TREE_NODE_SZ_SMALL)
                {
                    remainingCountLarge += count;
                }
                else
                {
                    remainingCountSmall += count;
                }
            }
        }

        if (remainingCount > 0)
        {
            jitprintf("    All other GT_xxx ...   %7u (%4.1lf%%) ... %4.1lf%% small + %4.1lf%% large\n", remainingCount,
                      100.0 * remainingCount / totalCount, 100.0 * remainingCountSmall / totalCount,
                      100.0 * remainingCountLarge / totalCount);
        }
        jitprintf("    -----------------------------------------------------\n");
        jitprintf("    Total    .......   %11u --ALL-- ... %4.1lf%% small + %4.1lf%% large\n", totalCount,
                  100.0 * countSmall / totalCount, 100.0 * countLarge / totalCount);
        jitprintf("\n");
    }

#endif // COUNT_AST_OPERS

#if DISPLAY_SIZES

    if (grossVMsize && grossNCsize)
    {
        jitprintf("\n");
        jitprintf("--------------------------------------\n");
        jitprintf("Function and GC info size stats\n");
        jitprintf("--------------------------------------\n");

        jitprintf("[%7u VM, %8u %6s %4u%%] %s\n", grossVMsize, grossNCsize, Target::g_tgtCPUName,
                  100 * grossNCsize / grossVMsize, "Total (excluding GC info)");

        jitprintf("[%7u VM, %8u %6s %4u%%] %s\n", grossVMsize, totalNCsize, Target::g_tgtCPUName,
                  100 * totalNCsize / grossVMsize, "Total (including GC info)");

        if (gcHeaderISize || gcHeaderNSize)
        {
            jitprintf("\n");

            jitprintf("GC tables   : [%7uI,%7uN] %7u byt  (%u%% of IL, %u%% of %s).\n", gcHeaderISize + gcPtrMapISize,
                      gcHeaderNSize + gcPtrMapNSize, totalNCsize - grossNCsize,
                      100 * (totalNCsize - grossNCsize) / grossVMsize, 100 * (totalNCsize - grossNCsize) / grossNCsize,
                      Target::g_tgtCPUName);

            jitprintf("GC headers  : [%7uI,%7uN] %7u byt, [%4.1fI,%4.1fN] %4.1f byt/meth\n", gcHeaderISize,
                      gcHeaderNSize, gcHeaderISize + gcHeaderNSize, (float)gcHeaderISize / (genMethodICnt + 0.001),
                      (float)gcHeaderNSize / (genMethodNCnt + 0.001),
                      (float)(gcHeaderISize + gcHeaderNSize) / genMethodCnt);

            jitprintf("GC ptr maps : [%7uI,%7uN] %7u byt, [%4.1fI,%4.1fN] %4.1f byt/meth\n", gcPtrMapISize,
                      gcPtrMapNSize, gcPtrMapISize + gcPtrMapNSize, (float)gcPtrMapISize / (genMethodICnt + 0.001),
                      (float)gcPtrMapNSize / (genMethodNCnt + 0.001),
                      (float)(gcPtrMapISize + gcPtrMapNSize) / genMethodCnt);
        }
        else
        {
            jitprintf("\n");

            jitprintf("GC tables   take up %u bytes (%u%% of instr, %u%% of %6s code).\n", totalNCsize - grossNCsize,
                      100 * (totalNCsize - grossNCsize) / grossVMsize, 100 * (totalNCsize - grossNCsize) / grossNCsize,
                      Target::g_tgtCPUName);
        }

#ifdef DEBUG
#if DOUBLE_ALIGN
        jitprintf("%u out of %u methods generated with double-aligned stack\n", Compiler::s_lvaDoubleAlignedProcsCount,
                  genMethodCnt);
#endif
#endif
    }

#endif // DISPLAY_SIZES

#if CALL_ARG_STATS
    compDispCallArgStats(jitstdout());
#endif

#if COUNT_BASIC_BLOCKS
    jitprintf("--------------------------------------------------\n");
    jitprintf("Basic block count frequency table:\n");
    jitprintf("--------------------------------------------------\n");
    bbCntTable.dump(jitstdout());
    jitprintf("--------------------------------------------------\n");

    jitprintf("\n");

    jitprintf("--------------------------------------------------\n");
    jitprintf("IL method size frequency table for methods with a single basic block:\n");
    jitprintf("--------------------------------------------------\n");
    bbOneBBSizeTable.dump(jitstdout());
    jitprintf("--------------------------------------------------\n");

    jitprintf("--------------------------------------------------\n");
    jitprintf("fgComputeReachabilitySets `while (change)` iterations:\n");
    jitprintf("--------------------------------------------------\n");
    computeReachabilitySetsIterationTable.dump(jitstdout());
    jitprintf("--------------------------------------------------\n");

#endif // COUNT_BASIC_BLOCKS

#if MEASURE_NODE_SIZE

    jitprintf("\n");
    jitprintf("---------------------------------------------------\n");
    jitprintf("GenTree node allocation stats\n");
    jitprintf("---------------------------------------------------\n");

    jitprintf("Allocated %6I64u tree nodes (%7I64u bytes total, avg %4I64u bytes per method)\n",
              genNodeSizeStats.genTreeNodeCnt, genNodeSizeStats.genTreeNodeSize,
              genNodeSizeStats.genTreeNodeSize / genMethodCnt);

    jitprintf("Allocated %7I64u bytes of unused tree node space (%3.2f%%)\n",
              genNodeSizeStats.genTreeNodeSize - genNodeSizeStats.genTreeNodeActualSize,
              (float)(100 * (genNodeSizeStats.genTreeNodeSize - genNodeSizeStats.genTreeNodeActualSize)) /
                  genNodeSizeStats.genTreeNodeSize);

    jitprintf("\n");
    jitprintf("---------------------------------------------------\n");
    jitprintf("Distribution of per-method GenTree node counts:\n");
    genTreeNcntHist.dump(jitstdout());

    jitprintf("\n");
    jitprintf("---------------------------------------------------\n");
    jitprintf("Distribution of per-method GenTree node  allocations (in bytes):\n");
    genTreeNsizHist.dump(jitstdout());

#endif // MEASURE_NODE_SIZE

#if MEASURE_BLOCK_SIZE

    jitprintf("\n");
    jitprintf("---------------------------------------------------\n");
    jitprintf("BasicBlock and FlowEdge/BasicBlockList allocation stats\n");
    jitprintf("---------------------------------------------------\n");

    jitprintf("Allocated %6u basic blocks (%7u bytes total, avg %4u bytes per method)\n", BasicBlock::s_Count,
              BasicBlock::s_Size, BasicBlock::s_Size / genMethodCnt);
    jitprintf("Allocated %6u flow nodes (%7u bytes total, avg %4u bytes per method)\n", genFlowNodeCnt, genFlowNodeSize,
              genFlowNodeSize / genMethodCnt);

#endif // MEASURE_BLOCK_SIZE

#if MEASURE_MEM_ALLOC

    if (s_dspMemStats)
    {
        jitprintf("\nAll allocations:\n");
        ArenaAllocator::dumpAggregateMemStats(jitstdout());

        jitprintf("\nLargest method:\n");
        ArenaAllocator::dumpMaxMemStats(jitstdout());

        jitprintf("\n");
        jitprintf("---------------------------------------------------\n");
        jitprintf("Distribution of total memory allocated per method (in KB):\n");
        memAllocHist.dump(jitstdout());

        jitprintf("\n");
        jitprintf("---------------------------------------------------\n");
        jitprintf("Distribution of total memory used      per method (in KB):\n");
        memUsedHist.dump(jitstdout());
    }

#endif // MEASURE_MEM_ALLOC

#if LOOP_HOIST_STATS
#ifdef DEBUG // Always display loop stats in retail
    if (JitConfig.DisplayLoopHoistStats() != 0)
#endif // DEBUG
    {
        PrintAggregateLoopHoistStats(jitstdout());
    }
#endif // LOOP_HOIST_STATS

#if TRACK_ENREG_STATS
    if (JitConfig.JitEnregStats() != 0)
    {
        s_enregisterStats.Dump(jitstdout());
    }
#endif // TRACK_ENREG_STATS

#if MEASURE_PTRTAB_SIZE

    jitprintf("\n");
    jitprintf("---------------------------------------------------\n");
    jitprintf("GC pointer table stats\n");
    jitprintf("---------------------------------------------------\n");

    jitprintf("Reg pointer descriptor size (internal): %8u (avg %4u per method)\n", GCInfo::s_gcRegPtrDscSize,
              GCInfo::s_gcRegPtrDscSize / genMethodCnt);

    jitprintf("Total pointer table size: %8u (avg %4u per method)\n", GCInfo::s_gcTotalPtrTabSize,
              GCInfo::s_gcTotalPtrTabSize / genMethodCnt);

#endif // MEASURE_PTRTAB_SIZE

#if MEASURE_NODE_SIZE || MEASURE_BLOCK_SIZE || MEASURE_PTRTAB_SIZE || DISPLAY_SIZES

    if (genMethodCnt != 0)
    {
        jitprintf("\n");
        jitprintf("A total of %6u methods compiled", genMethodCnt);
#if DISPLAY_SIZES
        if (genMethodICnt || genMethodNCnt)
        {
            jitprintf(" (%u interruptible, %u non-interruptible)", genMethodICnt, genMethodNCnt);
        }
#endif // DISPLAY_SIZES
        jitprintf(".\n");
    }

#endif // MEASURE_NODE_SIZE || MEASURE_BLOCK_SIZE || MEASURE_PTRTAB_SIZE || DISPLAY_SIZES

#if EMITTER_STATS
    emitterStats(jitstdout());
#endif

#if MEASURE_FATAL
    jitprintf("\n");
    jitprintf("---------------------------------------------------\n");
    jitprintf("Fatal errors stats\n");
    jitprintf("---------------------------------------------------\n");
    jitprintf("   badCode:             %u\n", fatal_badCode);
    jitprintf("   noWay:               %u\n", fatal_noWay);
    jitprintf("   implLimitation:      %u\n", fatal_implLimitation);
    jitprintf("   NOMEM:               %u\n", fatal_NOMEM);
    jitprintf("   noWayAssertBody:     %u\n", fatal_noWayAssertBody);
#ifdef DEBUG
    jitprintf("   noWayAssertBodyArgs: %u\n", fatal_noWayAssertBodyArgs);
#endif // DEBUG
    jitprintf("   NYI:                 %u\n", fatal_NYI);
#endif // MEASURE_FATAL

#if CALL_ARG_STATS || COUNT_BASIC_BLOCKS || EMITTER_STATS || MEASURE_NODE_SIZE || MEASURE_MEM_ALLOC
    DumpOnShutdown::DumpAll();
#endif
}

/*****************************************************************************
 *  Display static data structure sizes.
 */

/* static */
void Compiler::compDisplayStaticSizes()
{
#if MEASURE_NODE_SIZE
    GenTree::DumpNodeSizes();
#endif

#if EMITTER_STATS
    emitterStaticStats();
#endif
}

/*****************************************************************************
 *
 *  Destructor
 */

void Compiler::compDone()
{
#ifdef LATE_DISASM
    if (codeGen != nullptr)
    {
        codeGen->getDisAssembler().disDone();
    }
#endif // LATE_DISASM
}

CORINFO_CONST_LOOKUP Compiler::compGetHelperFtn(CorInfoHelpFunc ftnNum)
{
    CORINFO_CONST_LOOKUP lookup;

    if (info.compMatchedVM)
    {
        info.compCompHnd->getHelperFtn(ftnNum, &lookup);
        // The JIT only expects these two possible access types
        assert(lookup.accessType == IAT_VALUE || lookup.accessType == IAT_PVALUE);
    }
    else
    {
        // If we don't have a matched VM, we won't get valid results when asking for a helper function.
        lookup.addr       = (void*)(uintptr_t)(0xCA11CA11); // "callcall"
        lookup.accessType = IAT_VALUE;
    }

    return lookup;
}

unsigned Compiler::compGetTypeSize(CorInfoType cit, CORINFO_CLASS_HANDLE clsHnd)
{
    var_types sigType = genActualType(JITtype2varType(cit));
    unsigned  sigSize;
    sigSize = genTypeSize(sigType);
    if (cit == CORINFO_TYPE_VALUECLASS)
    {
        sigSize = info.compCompHnd->getClassSize(clsHnd);
    }
    else if (cit == CORINFO_TYPE_REFANY)
    {
        sigSize = 2 * TARGET_POINTER_SIZE;
    }
    return sigSize;
}

#ifdef DEBUG
static bool DidComponentUnitTests = false;

void Compiler::compDoComponentUnitTestsOnce()
{
    if (!JitConfig.RunComponentUnitTests())
    {
        return;
    }

    if (!DidComponentUnitTests)
    {
        DidComponentUnitTests = true;
        ValueNumStore::RunTests(this);
        BitSetSupport::TestSuite(getAllocatorDebugOnly());
    }
}

//------------------------------------------------------------------------
// compGetJitDefaultFill:
//
// Return Value:
//    An unsigned char value used to initialize memory allocated by the JIT.
//    The default value is taken from DOTNET_JitDefaultFill. If it is not set
//    the value will be 0xdd. When JitStress is active a random value based
//    on the method hash is used.
//
// Notes:
//    Note that we can't use small values like zero, because we have some
//    asserts that can fire for such values.
//
// static
unsigned char Compiler::compGetJitDefaultFill(Compiler* comp)
{
    unsigned char defaultFill = (unsigned char)JitConfig.JitDefaultFill();

    if (comp != nullptr && comp->compStressCompile(STRESS_GENERIC_VARN, 50))
    {
        unsigned temp;
        temp = comp->info.compMethodHash();
        temp = (temp >> 16) ^ temp;
        temp = (temp >> 8) ^ temp;
        temp = temp & 0xff;
        // asserts like this: assert(!IsUninitialized(stkLvl));
        // mean that small values for defaultFill are problematic
        // so we make the value larger in that case.
        if (temp < 0x20)
        {
            temp |= 0x80;
        }

        // Make a misaligned pointer value to reduce probability of getting a valid value and firing
        // assert(!IsUninitialized(pointer)).
        temp |= 0x1;

        defaultFill = (unsigned char)temp;
    }

    return defaultFill;
}

/*****************************************************************************/

VarName Compiler::compVarName(regNumber reg, bool isFloatReg)
{
    if (isFloatReg)
    {
        assert(genIsValidFloatReg(reg));
    }
    else
    {
        assert(genIsValidReg(reg));
    }

    if ((info.compVarScopesCount > 0) && compCurBB && opts.varNames)
    {
        unsigned   lclNum;
        LclVarDsc* varDsc;

        /* Look for the matching register */
        for (lclNum = 0, varDsc = lvaTable; lclNum < lvaCount; lclNum++, varDsc++)
        {
            /* If the variable is not in a register, or not in the register we're looking for, quit. */
            /* Also, if it is a compiler generated variable (i.e. slot# > info.compVarScopesCount), don't bother. */
            if ((varDsc->lvRegister != 0) && (varDsc->GetRegNum() == reg) &&
                (varDsc->lvSlotNum < info.compVarScopesCount))
            {
                /* check if variable in that register is live */
                if (VarSetOps::IsMember(this, compCurLife, varDsc->lvVarIndex))
                {
                    /* variable is live - find the corresponding slot */
                    VarScopeDsc* varScope =
                        compFindLocalVar(varDsc->lvSlotNum, compCurBB->bbCodeOffs, compCurBB->bbCodeOffsEnd);
                    if (varScope)
                    {
                        return varScope->vsdName;
                    }
                }
            }
        }
    }

    return nullptr;
}

#endif // DEBUG

const char* Compiler::compRegVarName(regNumber reg, bool displayVar, bool isFloatReg)
{
#ifdef TARGET_ARM
    isFloatReg = genIsValidFloatReg(reg);
#endif

#ifdef DEBUG
    if (displayVar && (reg != REG_NA))
    {
        VarName varName = compVarName(reg, isFloatReg);

        if (varName)
        {
            const int   NAME_VAR_REG_BUFFER_LEN = 4 + 256 + 1;
            static char nameVarReg[2][NAME_VAR_REG_BUFFER_LEN]; // to avoid overwriting the buffer when have 2
                                                                // consecutive calls before printing
            static int index = 0;                               // for circular index into the name array

            index ^= 1; // circular reuse of index
            sprintf_s(nameVarReg[index], NAME_VAR_REG_BUFFER_LEN, "%s'%s'", getRegName(reg), VarNameToStr(varName));

            return nameVarReg[index];
        }
    }
#endif

    /* no debug info required or no variable in that register
       -> return standard name */

    return getRegName(reg);
}

const char* Compiler::compRegNameForSize(regNumber reg, size_t size)
{
#if CPU_HAS_BYTE_REGS
    if (size == 1 || size == 2)
    {
        // clang-format off
        static const char* sizeNames[][2] =
        {
            { "al", "ax" },
            { "cl", "cx" },
            { "dl", "dx" },
            { "bl", "bx" },
    #ifdef TARGET_AMD64
            {  "spl",   "sp" }, // ESP
            {  "bpl",   "bp" }, // EBP
            {  "sil",   "si" }, // ESI
            {  "dil",   "di" }, // EDI
            {  "r8b",  "r8w" },
            {  "r9b",  "r9w" },
            { "r10b", "r10w" },
            { "r11b", "r11w" },
            { "r12b", "r12w" },
            { "r13b", "r13w" },
            { "r14b", "r14w" },
            { "r15b", "r15w" },
    #endif // TARGET_AMD64
        };
        // clang-format on

        assert(isByteReg(reg));
        assert(genRegMask(reg) & RBM_BYTE_REGS);
        assert(size == 1 || size == 2);

        return sizeNames[reg][size - 1];
    }
#endif // CPU_HAS_BYTE_REGS

    return compRegVarName(reg, true);
}

#ifdef DEBUG
const char* Compiler::compLocalVarName(unsigned varNum, unsigned offs)
{
    unsigned     i;
    VarScopeDsc* t;

    for (i = 0, t = info.compVarScopes; i < info.compVarScopesCount; i++, t++)
    {
        if (t->vsdVarNum != varNum)
        {
            continue;
        }

        if (offs >= t->vsdLifeBeg && offs < t->vsdLifeEnd)
        {
            return VarNameToStr(t->vsdName);
        }
    }

    return nullptr;
}
#endif

/*****************************************************************************/

void Compiler::compSetProcessor()
{
    //
    // NOTE: This function needs to be kept in sync with EEJitManager::SetCpuInfo() in vm\codeman.cpp
    //

    const JitFlags& jitFlags = *opts.jitFlags;

    //
    // Processor specific optimizations
    //

    CORINFO_InstructionSetFlags instructionSetFlags = jitFlags.GetInstructionSetFlags();
    opts.compSupportsISA.Reset();
    opts.compSupportsISAReported.Reset();
    opts.compSupportsISAExactly.Reset();

    // The VM will set the ISA flags depending on actual hardware support and any
    // config values specified by the user. Config may cause the VM to omit baseline
    // ISAs from the supported set. We force their inclusion here so that JIT code
    // can use them unconditionally, but we will honor the config when resolving
    // managed HWIntrinsic methods.
    //
    // We also take care of adding the virtual vector ISAs (i.e. Vector64/128/256/512)
    // here, based on the combination of hardware ISA support and config values.

#if defined(TARGET_XARCH)
    // If the VM passed in a virtual vector ISA, it was done to communicate PreferredVectorBitWidth.
    // No check is done for the validity of the value, since it will be clamped to max supported by
    // hardware and config when queried.  We will, therefore, remove the marker ISA and allow it to
    // be re-added if appropriate based on the hardware ISA evaluations below.

    uint32_t preferredVectorBitWidth = 0;
    if (instructionSetFlags.HasInstructionSet(InstructionSet_Vector128))
    {
        instructionSetFlags.RemoveInstructionSet(InstructionSet_Vector128);
        preferredVectorBitWidth = 128;
    }
    else if (instructionSetFlags.HasInstructionSet(InstructionSet_Vector256))
    {
        instructionSetFlags.RemoveInstructionSet(InstructionSet_Vector256);
        preferredVectorBitWidth = 256;
    }
    else if (instructionSetFlags.HasInstructionSet(InstructionSet_Vector512))
    {
        instructionSetFlags.RemoveInstructionSet(InstructionSet_Vector512);
        preferredVectorBitWidth = 512;
    }

    opts.preferredVectorByteLength = preferredVectorBitWidth / BITS_PER_BYTE;

    // Only one marker ISA should have been passed in, and it should now be cleared.
    assert(!instructionSetFlags.HasInstructionSet(InstructionSet_Vector128) &&
           !instructionSetFlags.HasInstructionSet(InstructionSet_Vector256) &&
           !instructionSetFlags.HasInstructionSet(InstructionSet_Vector512));

    // Ensure required baseline ISAs are supported in JIT code, even if not passed in by the VM.
    instructionSetFlags.AddInstructionSet(InstructionSet_X86Base);
#ifdef TARGET_AMD64
    instructionSetFlags.AddInstructionSet(InstructionSet_X86Base_X64);
#endif // TARGET_AMD64

    // We can now add the virtual vector ISAs as appropriate. Vector128 is part of the required baseline.
    instructionSetFlags.AddInstructionSet(InstructionSet_Vector128);

    if (instructionSetFlags.HasInstructionSet(InstructionSet_AVX))
    {
        instructionSetFlags.AddInstructionSet(InstructionSet_Vector256);
    }

    if (instructionSetFlags.HasInstructionSet(InstructionSet_AVX512))
    {
        instructionSetFlags.AddInstructionSet(InstructionSet_Vector512);
    }
#elif defined(TARGET_ARM64)
    // Ensure required baseline ISAs are supported in JIT code, even if not passed in by the VM.
    instructionSetFlags.AddInstructionSet(InstructionSet_ArmBase);
    instructionSetFlags.AddInstructionSet(InstructionSet_ArmBase_Arm64);
    instructionSetFlags.AddInstructionSet(InstructionSet_AdvSimd);
    instructionSetFlags.AddInstructionSet(InstructionSet_AdvSimd_Arm64);

    // Add virtual vector ISAs. These are both supported as part of the required baseline.
    instructionSetFlags.AddInstructionSet(InstructionSet_Vector64);
    instructionSetFlags.AddInstructionSet(InstructionSet_Vector128);
#endif // TARGET_ARM64

    assert(instructionSetFlags.Equals(EnsureInstructionSetFlagsAreValid(instructionSetFlags)));
    opts.setSupportedISAs(instructionSetFlags);

#ifdef TARGET_XARCH
    if (!compIsForInlining())
    {
        if (canUseVexEncoding())
        {
            codeGen->GetEmitter()->SetUseVEXEncoding(true);
            // Assume each JITted method does not contain AVX instruction at first
            codeGen->GetEmitter()->SetContainsAVX(false);
            codeGen->GetEmitter()->SetContains256bitOrMoreAVX(false);
            codeGen->GetEmitter()->SetContainsCallNeedingVzeroupper(false);
        }
        if (canUseEvexEncoding())
        {
            codeGen->GetEmitter()->SetUseEvexEncoding(true);
        }
        if (canUseApxEncoding())
        {
            codeGen->GetEmitter()->SetUseRex2Encoding(true);
            codeGen->GetEmitter()->SetUsePromotedEVEXEncoding(true);
        }
    }
#endif // TARGET_XARCH
}

bool Compiler::notifyInstructionSetUsage(CORINFO_InstructionSet isa, bool supported) const
{
    const char* isaString = InstructionSetToString(isa);
    JITDUMP("Notify VM instruction set (%s) %s be supported.\n", isaString, supported ? "must" : "must not");
    return info.compCompHnd->notifyInstructionSetUsage(isa, supported);
}

#ifdef PROFILING_SUPPORTED
// A Dummy routine to receive Enter/Leave/Tailcall profiler callbacks.
// These are used when DOTNET_JitEltHookEnabled=1
#ifdef TARGET_AMD64
void DummyProfilerELTStub(UINT_PTR ProfilerHandle, UINT_PTR callerSP)
{
    return;
}
#else  //! TARGET_AMD64
void DummyProfilerELTStub(UINT_PTR ProfilerHandle)
{
    return;
}
#endif //! TARGET_AMD64

#endif // PROFILING_SUPPORTED

bool Compiler::compShouldThrowOnNoway()
{
    // In min opts, we don't want the noway assert to go through the exception
    // path. Instead we want it to just silently go through codegen for
    // compat reasons.
    return !opts.MinOpts();
}

// ConfigInteger does not offer an option for decimal flags.  Any numbers are interpreted as hex.
// I could add the decimal option to ConfigInteger or I could write a function to reinterpret this
// value as the user intended.
unsigned ReinterpretHexAsDecimal(unsigned in)
{
    // ex: in: 0x100 returns: 100
    unsigned result = 0;
    unsigned index  = 1;

    // default value
    if (in == INT_MAX)
    {
        return in;
    }

    while (in)
    {
        unsigned digit = in % 16;
        in >>= 4;
        assert(digit < 10);
        result += digit * index;
        index *= 10;
    }
    return result;
}

void Compiler::compInitOptions(JitFlags* jitFlags)
{
    opts = {};

    if (compIsForInlining())
    {
        // The following flags are lost when inlining. (They are removed in
        // Compiler::fgInvokeInlineeCompiler().)
        assert(!jitFlags->IsSet(JitFlags::JIT_FLAG_PROF_ENTERLEAVE));
        assert(!jitFlags->IsSet(JitFlags::JIT_FLAG_DEBUG_EnC));
        assert(!jitFlags->IsSet(JitFlags::JIT_FLAG_REVERSE_PINVOKE));
        assert(!jitFlags->IsSet(JitFlags::JIT_FLAG_TRACK_TRANSITIONS));
    }

    opts.jitFlags  = jitFlags;
    opts.compFlags = CLFLG_MAXOPT; // Default value is for full optimization

    if (jitFlags->IsSet(JitFlags::JIT_FLAG_DEBUG_CODE) || jitFlags->IsSet(JitFlags::JIT_FLAG_MIN_OPT) ||
        jitFlags->IsSet(JitFlags::JIT_FLAG_TIER0))
    {
        opts.compFlags = CLFLG_MINOPT;
    }

    // Default value is to generate a blend of size and speed optimizations
    //
    opts.compCodeOpt = BLENDED_CODE;

    // If the EE sets SIZE_OPT or if we are compiling a Class constructor
    // we will optimize for code size at the expense of speed
    //
    if (jitFlags->IsSet(JitFlags::JIT_FLAG_SIZE_OPT) || ((info.compFlags & FLG_CCTOR) == FLG_CCTOR))
    {
        opts.compCodeOpt = SMALL_CODE;
    }
    //
    // If the EE sets SPEED_OPT we will optimize for speed at the expense of code size
    //
    else if (jitFlags->IsSet(JitFlags::JIT_FLAG_SPEED_OPT) ||
             (jitFlags->IsSet(JitFlags::JIT_FLAG_TIER1) && !jitFlags->IsSet(JitFlags::JIT_FLAG_MIN_OPT)))
    {
        opts.compCodeOpt = FAST_CODE;
        assert(!jitFlags->IsSet(JitFlags::JIT_FLAG_SIZE_OPT));
    }

    //-------------------------------------------------------------------------

    opts.compDbgCode = jitFlags->IsSet(JitFlags::JIT_FLAG_DEBUG_CODE);
    opts.compDbgInfo = jitFlags->IsSet(JitFlags::JIT_FLAG_DEBUG_INFO);
    opts.compDbgEnC  = jitFlags->IsSet(JitFlags::JIT_FLAG_DEBUG_EnC);

#ifdef DEBUG
    opts.compJitAlignLoopAdaptive       = JitConfig.JitAlignLoopAdaptive() == 1;
    opts.compJitAlignLoopBoundary       = (unsigned short)JitConfig.JitAlignLoopBoundary();
    opts.compJitAlignLoopMinBlockWeight = (unsigned short)JitConfig.JitAlignLoopMinBlockWeight();

    opts.compJitAlignLoopForJcc             = JitConfig.JitAlignLoopForJcc() == 1;
    opts.compJitAlignLoopMaxCodeSize        = (unsigned short)JitConfig.JitAlignLoopMaxCodeSize();
    opts.compJitHideAlignBehindJmp          = JitConfig.JitHideAlignBehindJmp() == 1;
    opts.compJitOptimizeStructHiddenBuffer  = JitConfig.JitOptimizeStructHiddenBuffer() == 1;
    opts.compJitUnrollLoopMaxIterationCount = (unsigned short)JitConfig.JitUnrollLoopMaxIterationCount();
#else
    opts.compJitAlignLoopAdaptive           = true;
    opts.compJitAlignLoopBoundary           = DEFAULT_ALIGN_LOOP_BOUNDARY;
    opts.compJitAlignLoopMinBlockWeight     = DEFAULT_ALIGN_LOOP_MIN_BLOCK_WEIGHT;
    opts.compJitAlignLoopMaxCodeSize        = DEFAULT_MAX_LOOPSIZE_FOR_ALIGN;
    opts.compJitHideAlignBehindJmp          = true;
    opts.compJitOptimizeStructHiddenBuffer  = true;
    opts.compJitUnrollLoopMaxIterationCount = DEFAULT_UNROLL_LOOP_MAX_ITERATION_COUNT;
#endif

#ifdef TARGET_XARCH
    if (opts.compJitAlignLoopAdaptive)
    {
        // For adaptive alignment, padding limit is equal to the max instruction encoding
        // size which is 15 bytes. Hence (32 >> 1) - 1 = 15 bytes.
        opts.compJitAlignPaddingLimit = (opts.compJitAlignLoopBoundary >> 1) - 1;
    }
    else
    {
        // For non-adaptive alignment, padding limit is 1 less than the alignment boundary
        // specified.
        opts.compJitAlignPaddingLimit = opts.compJitAlignLoopBoundary - 1;
    }
#elif TARGET_ARM64
    if (opts.compJitAlignLoopAdaptive)
    {
        // For adaptive alignment, padding limit is same as specified by the alignment
        // boundary because all instructions are 4 bytes long. Hence (32 >> 1) = 16 bytes.
        opts.compJitAlignPaddingLimit = (opts.compJitAlignLoopBoundary >> 1);
    }
    else
    {
        // For non-adaptive, padding limit is same as specified by the alignment.
        opts.compJitAlignPaddingLimit = opts.compJitAlignLoopBoundary;
    }
#endif

    assert(isPow2(opts.compJitAlignLoopBoundary));
#ifdef TARGET_ARM64
    // The minimum encoding size for Arm64 is 4 bytes.
    assert(opts.compJitAlignLoopBoundary >= 4);
#endif

#if REGEN_SHORTCUTS || REGEN_CALLPAT
    // We never want to have debugging enabled when regenerating GC encoding patterns
    opts.compDbgCode = false;
    opts.compDbgInfo = false;
    opts.compDbgEnC  = false;
#endif

    compSetProcessor();

#ifdef DEBUG
    opts.dspOrder = false;

    // Optionally suppress inliner compiler instance dumping.
    //
    if (compIsForInlining())
    {
        if (JitConfig.JitDumpInlinePhases() > 0)
        {
            verbose = impInlineInfo->InlinerCompiler->verbose;
        }
        else
        {
            verbose = false;
        }
    }
    else
    {
        verbose = false;
        codeGen->setVerbose(false);
    }
    verboseTrees     = verbose && shouldUseVerboseTrees();
    verboseSsa       = verbose && shouldUseVerboseSsa();
    asciiTrees       = shouldDumpASCIITrees();
    opts.dspDiffable = compIsForInlining() ? impInlineInfo->InlinerCompiler->opts.dspDiffable : false;

#endif

    opts.altJit = false;

#if defined(LATE_DISASM) && !defined(DEBUG)
    // For non-debug builds with the late disassembler built in, we currently always do late disassembly
    // (we have no way to determine when not to, since we don't have class/method names).
    // In the DEBUG case, this is initialized to false, below.
    opts.doLateDisasm = true;
#endif

#ifdef DEBUG

    const JitConfigValues::MethodSet* pfAltJit;
    if (jitFlags->IsSet(JitFlags::JIT_FLAG_AOT))
    {
        pfAltJit = &JitConfig.AltJitNgen();
    }
    else
    {
        pfAltJit = &JitConfig.AltJit();
    }

    if (jitFlags->IsSet(JitFlags::JIT_FLAG_ALT_JIT))
    {
        if (pfAltJit->contains(info.compMethodHnd, info.compClassHnd, &info.compMethodInfo->args))
        {
            opts.altJit = true;
        }

        unsigned altJitLimit = ReinterpretHexAsDecimal(JitConfig.AltJitLimit());
        if (altJitLimit > 0 && Compiler::jitTotalMethodCompiled >= altJitLimit)
        {
            opts.altJit = false;
        }
    }

#else // !DEBUG

    const char* altJitVal;
    if (jitFlags->IsSet(JitFlags::JIT_FLAG_AOT))
    {
        altJitVal = JitConfig.AltJitNgen().list();
    }
    else
    {
        altJitVal = JitConfig.AltJit().list();
    }

    if (jitFlags->IsSet(JitFlags::JIT_FLAG_ALT_JIT))
    {
        // In release mode, you either get all methods or no methods. You must use "*" as the parameter, or we ignore
        // it. You don't get to give a regular expression of methods to match.
        // (Partially, this is because we haven't computed and stored the method and class name except in debug, and it
        // might be expensive to do so.)
        if ((altJitVal != nullptr) && (strcmp(altJitVal, "*") == 0))
        {
            opts.altJit = true;
        }
    }

#endif // !DEBUG

    // Take care of DOTNET_AltJitExcludeAssemblies.
    if (opts.altJit)
    {
        // First, initialize the AltJitExcludeAssemblies list, but only do it once.
        if (!s_pAltJitExcludeAssembliesListInitialized)
        {
            const char* wszAltJitExcludeAssemblyList = JitConfig.AltJitExcludeAssemblies();
            if (wszAltJitExcludeAssemblyList != nullptr)
            {
                // NOTE: The Assembly name list is allocated in the process heap, not in the no-release heap, which is
                // reclaimed
                // for every compilation. This is ok because we only allocate once, due to the static.
                s_pAltJitExcludeAssembliesList = new (HostAllocator::getHostAllocator())
                    AssemblyNamesList2(wszAltJitExcludeAssemblyList, HostAllocator::getHostAllocator());
            }
            s_pAltJitExcludeAssembliesListInitialized = true;
        }

        if (s_pAltJitExcludeAssembliesList != nullptr)
        {
            // We have an exclusion list. See if this method is in an assembly that is on the list.
            // Note that we check this for every method, since we might inline across modules, and
            // if the inlinee module is on the list, we don't want to use the altjit for it.
            const char* methodAssemblyName = eeGetClassAssemblyName(info.compClassHnd);
            if (s_pAltJitExcludeAssembliesList->IsInList(methodAssemblyName))
            {
                opts.altJit = false;
            }
        }
    }

#ifdef DEBUG

    // Setup assembly name list for disassembly and dump, if not already set up.
    if (!s_pJitDisasmIncludeAssembliesListInitialized)
    {
        const char* assemblyNameList = JitConfig.JitDisasmAssemblies();
        if (assemblyNameList != nullptr)
        {
            s_pJitDisasmIncludeAssembliesList = new (HostAllocator::getHostAllocator())
                AssemblyNamesList2(assemblyNameList, HostAllocator::getHostAllocator());
        }
        s_pJitDisasmIncludeAssembliesListInitialized = true;
    }

    // Check for a specific set of assemblies to dump.
    // If we have an assembly name list for disassembly, also check this method's assembly.
    bool assemblyInIncludeList = true; // assume we'll dump, if there's not an include list (or it's empty).
    if (s_pJitDisasmIncludeAssembliesList != nullptr && !s_pJitDisasmIncludeAssembliesList->IsEmpty())
    {
        const char* assemblyName = eeGetClassAssemblyName(info.compClassHnd);
        if (!s_pJitDisasmIncludeAssembliesList->IsInList(assemblyName))
        {
            // We have a list, and the current assembly is not in it, so we won't dump.
            assemblyInIncludeList = false;
        }
    }

    bool altJitConfig = !pfAltJit->isEmpty();

    bool verboseDump = false;

    if (!altJitConfig || opts.altJit)
    {
        // We should only enable 'verboseDump' when we are actually compiling a matching method
        // and not enable it when we are just considering inlining a matching method.
        //
        if (!compIsForInlining())
        {
            if (JitConfig.JitDump().contains(info.compMethodHnd, info.compClassHnd, &info.compMethodInfo->args))
            {
                verboseDump = true;
            }
            unsigned jitHashDumpVal = (unsigned)JitConfig.JitHashDump();
            if ((jitHashDumpVal != (DWORD)-1) && (jitHashDumpVal == info.compMethodHash()))
            {
                verboseDump = true;
            }
        }
    }

    // Optionally suppress dumping if not in specified list of included assemblies.
    //
    if (verboseDump && !assemblyInIncludeList)
    {
        verboseDump = false;
    }

    // Optionally suppress dumping Tier0 jit requests.
    //
    if (verboseDump && jitFlags->IsSet(JitFlags::JIT_FLAG_TIER0))
    {
        verboseDump = (JitConfig.JitDumpTier0() > 0);
    }

    // Optionally suppress dumping OSR jit requests.
    //
    if (verboseDump && jitFlags->IsSet(JitFlags::JIT_FLAG_OSR))
    {
        verboseDump = (JitConfig.JitDumpOSR() > 0);
    }

    // Optionally suppress dumping except for a specific OSR jit request.
    //
    const int dumpAtOSROffset = JitConfig.JitDumpAtOSROffset();

    if (verboseDump && (dumpAtOSROffset != -1))
    {
        if (jitFlags->IsSet(JitFlags::JIT_FLAG_OSR))
        {
            verboseDump = (((IL_OFFSET)dumpAtOSROffset) == info.compILEntry);
        }
        else
        {
            verboseDump = false;
        }
    }

    if (verboseDump)
    {
        verbose = true;
    }
#endif // DEBUG

#ifdef FEATURE_SIMD
    setUsesSIMDTypes(false);
#endif // FEATURE_SIMD

    lvaEnregEHVars       = (compEnregLocals() && JitConfig.EnableEHWriteThru());
    lvaEnregMultiRegVars = (compEnregLocals() && JitConfig.EnableMultiRegLocals());

#if FEATURE_TAILCALL_OPT
    // By default opportunistic tail call optimization is enabled.
    // Recognition is done in the importer so this must be set for
    // inlinees as well.
    opts.compTailCallOpt = true;
#endif // FEATURE_TAILCALL_OPT

#if FEATURE_FASTTAILCALL
    // By default fast tail calls are enabled.
    opts.compFastTailCalls = true;
#endif // FEATURE_FASTTAILCALL

    // Profile data
    //
    fgPgoSchema      = nullptr;
    fgPgoData        = nullptr;
    fgPgoSchemaCount = 0;
    fgPgoQueryResult = E_FAIL;
    fgPgoFailReason  = nullptr;
    fgPgoSource      = ICorJitInfo::PgoSource::Unknown;
    fgPgoHaveWeights = false;
    fgPgoSynthesized = false;
    fgPgoConsistent  = false;
    fgPgoDynamic     = false;

    if (jitFlags->IsSet(JitFlags::JIT_FLAG_BBOPT))
    {
        fgPgoQueryResult =
            info.compCompHnd->getPgoInstrumentationResults(info.compMethodHnd, &fgPgoSchema, &fgPgoSchemaCount,
                                                           &fgPgoData, &fgPgoSource, &fgPgoDynamic);

        // a failed result that also has a non-NULL fgPgoSchema
        // indicates that the ILSize for the method no longer matches
        // the ILSize for the method when profile data was collected.
        //
        // We will discard the IBC data in this case
        //
        if (FAILED(fgPgoQueryResult))
        {
            fgPgoFailReason = (fgPgoSchema != nullptr) ? "No matching PGO data" : "No PGO data";
            fgPgoData       = nullptr;
            fgPgoSchema     = nullptr;
        }
        // Optionally, disable use of profile data.
        //
        else if (JitConfig.JitDisablePGO() > 0)
        {
            fgPgoFailReason  = "PGO data available, but JitDisablePGO > 0";
            fgPgoQueryResult = E_FAIL;
            fgPgoData        = nullptr;
            fgPgoSchema      = nullptr;
            fgPgoDisabled    = true;
            fgPgoDynamic     = false;
        }
#ifdef DEBUG
        // Optionally, enable use of profile data for only some methods.
        //
        else
        {
            static ConfigMethodRange JitEnablePGORange;
            JitEnablePGORange.EnsureInit(JitConfig.JitEnablePGORange());

            // Base this decision on the root method hash, so a method either sees all available
            // profile data (including that for inlinees), or none of it.
            //
            const unsigned hash = impInlineRoot()->info.compMethodHash();
            if (!JitEnablePGORange.Contains(hash))
            {
                fgPgoFailReason  = "PGO data available, but method hash NOT within JitEnablePGORange";
                fgPgoQueryResult = E_FAIL;
                fgPgoData        = nullptr;
                fgPgoSchema      = nullptr;
                fgPgoDisabled    = true;
            }
        }
#endif // DEBUG

        // A successful result implies a non-NULL fgPgoSchema
        //
        if (SUCCEEDED(fgPgoQueryResult))
        {
            assert(fgPgoSchema != nullptr);

            for (UINT32 i = 0; i < fgPgoSchemaCount; i++)
            {
                ICorJitInfo::PgoInstrumentationKind kind = fgPgoSchema[i].InstrumentationKind;
                if (kind == ICorJitInfo::PgoInstrumentationKind::BasicBlockIntCount ||
                    kind == ICorJitInfo::PgoInstrumentationKind::BasicBlockLongCount ||
                    kind == ICorJitInfo::PgoInstrumentationKind::EdgeIntCount ||
                    kind == ICorJitInfo::PgoInstrumentationKind::EdgeLongCount)
                {
                    fgPgoHaveWeights = true;
                    break;
                }
            }

            // Stash pointers to PGO info on the context so
            // we can access it contextually later.
            //
            compInlineContext->SetPgoInfo(PgoInfo(this));
        }

        // A failed result implies a NULL fgPgoSchema
        //   see implementation of Compiler::fgHaveProfileData()
        //
        if (FAILED(fgPgoQueryResult))
        {
            assert(fgPgoSchema == nullptr);
        }
    }

    bool enableInliningMethodsWithEH = JitConfig.JitInlineMethodsWithEH() > 0;

#ifdef DEBUG
    static ConfigMethodRange JitInlineMethodsWithEHRange;
    JitInlineMethodsWithEHRange.EnsureInit(JitConfig.JitInlineMethodsWithEHRange());
    const unsigned hash    = impInlineRoot()->info.compMethodHash();
    const bool     inRange = JitInlineMethodsWithEHRange.Contains(hash);
    enableInliningMethodsWithEH &= inRange;
#endif

    opts.compInlineMethodsWithEH = enableInliningMethodsWithEH;

    if (compIsForInlining())
    {
        return;
    }

    // The rest of the opts fields that we initialize here
    // should only be used when we generate code for the method
    // They should not be used when importing or inlining

#if FEATURE_TAILCALL_OPT
    opts.compTailCallLoopOpt = true;
#endif // FEATURE_TAILCALL_OPT

    opts.genFPorder = true;
    opts.genFPopt   = true;

    opts.instrCount     = 0;
    opts.callInstrCount = 0;
    opts.lvRefCount     = 0;

#ifdef PROFILING_SUPPORTED
    opts.compJitELTHookEnabled = false;
#endif // PROFILING_SUPPORTED

#if defined(TARGET_ARM64)
    // 0 is default: use the appropriate frame type based on the function.
    opts.compJitSaveFpLrWithCalleeSavedRegisters = 0;
#endif // defined(TARGET_ARM64)

    opts.disAsm       = false;
    opts.disDiffable  = false;
    opts.dspDiffable  = false;
    opts.disAlignment = false;
    opts.disCodeBytes = false;

    opts.optRepeat          = false;
    opts.optRepeatIteration = 0;
    opts.optRepeatCount     = 1;
    opts.optRepeatActive    = false;

#ifdef DEBUG
    opts.dspInstrs       = false;
    opts.dspLines        = false;
    opts.varNames        = false;
    opts.disAsmSpilled   = false;
    opts.disAddr         = false;
    opts.dspCode         = false;
    opts.dspEHTable      = false;
    opts.dspDebugInfo    = false;
    opts.dspGCtbls       = false;
    opts.dspMetrics      = false;
    opts.disAsm2         = false;
    opts.dspUnwind       = false;
    opts.compLongAddress = false;

#ifdef LATE_DISASM
    opts.doLateDisasm = false;
#endif // LATE_DISASM

    compDebugBreak = false;

    //  If we have a non-empty AltJit config then we change all of these other
    //  config values to refer only to the AltJit.
    //
    if (!altJitConfig || opts.altJit)
    {
        bool disEnabled = true;

        // Optionally suppress dumping if not in specified list of included assemblies.
        //
        if (!assemblyInIncludeList)
        {
            disEnabled = false;
        }

        if (disEnabled)
        {
            if ((JitConfig.JitOrder() & 1) == 1)
            {
                opts.dspOrder = true;
            }

            if (JitConfig.JitGCDump().contains(info.compMethodHnd, info.compClassHnd, &info.compMethodInfo->args))
            {
                opts.dspGCtbls = true;
            }

            if (JitConfig.JitDisasm().contains(info.compMethodHnd, info.compClassHnd, &info.compMethodInfo->args))
            {
                opts.disAsm = true;
            }

            if (JitConfig.JitDisasmSpilled())
            {
                opts.disAsmSpilled = true;
            }

            if (JitConfig.JitUnwindDump().contains(info.compMethodHnd, info.compClassHnd, &info.compMethodInfo->args))
            {
                opts.dspUnwind = true;
            }

            if (JitConfig.JitEHDump().contains(info.compMethodHnd, info.compClassHnd, &info.compMethodInfo->args))
            {
                opts.dspEHTable = true;
            }

            if (JitConfig.JitDebugDump().contains(info.compMethodHnd, info.compClassHnd, &info.compMethodInfo->args))
            {
                opts.dspDebugInfo = true;
            }
        }

        if (opts.disAsm && JitConfig.JitDisasmWithGC())
        {
            opts.disasmWithGC = true;
        }

#ifdef LATE_DISASM
        if (JitConfig.JitLateDisasm().contains(info.compMethodHnd, info.compClassHnd, &info.compMethodInfo->args))
        {
            opts.doLateDisasm = true;
        }
#endif // LATE_DISASM

        if (JitConfig.JitDasmWithAddress() != 0)
        {
            opts.disAddr = true;
        }

        if (JitConfig.JitLongAddress() != 0)
        {
            opts.compLongAddress = true;
        }

        if ((JitConfig.JitEnableOptRepeat() != 0) &&
            (JitConfig.JitOptRepeat().contains(info.compMethodHnd, info.compClassHnd, &info.compMethodInfo->args)))
        {
            opts.optRepeat      = true;
            opts.optRepeatCount = JitConfig.JitOptRepeatCount();
        }

        opts.dspMetrics = (JitConfig.JitMetrics() != 0);
    }

    if (verboseDump)
    {
        opts.dspCode    = true;
        opts.dspEHTable = true;
        opts.dspGCtbls  = true;
        opts.disAsm2    = true;
        opts.dspUnwind  = true;
        verbose         = true;
        verboseTrees    = shouldUseVerboseTrees();
        verboseSsa      = shouldUseVerboseSsa();
        codeGen->setVerbose(true);
    }

    treesBeforeAfterMorph = (JitConfig.JitDumpBeforeAfterMorph() == 1);
    morphNum              = 0; // Initialize the morphed-trees counting.

    expensiveDebugCheckLevel = JitConfig.JitExpensiveDebugCheckLevel();
    if (expensiveDebugCheckLevel == 0)
    {
        // If we're in a stress mode that modifies the flowgraph, make 1 the default.
        if (fgStressBBProf() || compStressCompile(STRESS_DO_WHILE_LOOPS, 30))
        {
            expensiveDebugCheckLevel = 1;
        }
    }

    if (verbose)
    {
        printf("****** START compiling %s (MethodHash=%08x)\n", info.compFullName, info.compMethodHash());
        printf("Generating code for %s %s\n", Target::g_tgtPlatformName(), Target::g_tgtCPUName);
        printf(""); // in our logic this causes a flush
    }

    if (JitConfig.JitBreak().contains(info.compMethodHnd, info.compClassHnd, &info.compMethodInfo->args))
    {
        assert(!"JitBreak reached");
    }

    unsigned jitHashBreakVal = (unsigned)JitConfig.JitHashBreak();
    if ((jitHashBreakVal != (DWORD)-1) && (jitHashBreakVal == info.compMethodHash()))
    {
        assert(!"JitHashBreak reached");
    }

    if (verbose ||
        JitConfig.JitDebugBreak().contains(info.compMethodHnd, info.compClassHnd, &info.compMethodInfo->args) ||
        JitConfig.JitBreak().contains(info.compMethodHnd, info.compClassHnd, &info.compMethodInfo->args))
    {
        compDebugBreak = true;
    }

    memset(compActiveStressModes, 0, sizeof(compActiveStressModes));

    // Read function list, if not already read, and there exists such a list.
    if (!s_pJitFunctionFileInitialized)
    {
        const char* functionFileName = JitConfig.JitFunctionFile();
        if (functionFileName != nullptr)
        {
            s_pJitMethodSet =
                new (HostAllocator::getHostAllocator()) MethodSet(functionFileName, HostAllocator::getHostAllocator());
        }
        s_pJitFunctionFileInitialized = true;
    }
#else  // DEBUG
    if (JitConfig.JitDisasm().contains(info.compMethodHnd, info.compClassHnd, &info.compMethodInfo->args))
    {
        opts.disAsm = true;
    }

    if ((JitConfig.JitEnableOptRepeat() != 0) &&
        (JitConfig.JitOptRepeat().contains(info.compMethodHnd, info.compClassHnd, &info.compMethodInfo->args)))
    {
        opts.optRepeat      = true;
        opts.optRepeatCount = JitConfig.JitOptRepeatCount();
    }
#endif // !DEBUG

#ifndef DEBUG
    if (opts.disAsm)
#endif
    {
        if (JitConfig.JitDisasmTesting())
        {
            opts.disTesting = true;
        }
        if (JitConfig.JitDisasmWithAlignmentBoundaries())
        {
            opts.disAlignment = true;
        }
        if (JitConfig.JitDisasmWithCodeBytes())
        {
            opts.disCodeBytes = true;
        }
        if (JitConfig.JitDisasmDiffable())
        {
            opts.disDiffable = true;
            opts.dspDiffable = true;
        }
    }

    if (opts.optRepeat)
    {
        // Defer printing this until now, after the "START" line printed above.
        JITDUMP("\n*************** JitOptRepeat enabled; repetition count: %d\n\n", opts.optRepeatCount);
    }
    else if (JitConfig.JitEnableOptRepeat() != 0)
    {
#ifdef DEBUG
        // Opt-in to JitOptRepeat based on method hash ranges.
        // The default is no JitOptRepeat.
        static ConfigMethodRange fJitOptRepeatRange;
        fJitOptRepeatRange.EnsureInit(JitConfig.JitOptRepeatRange());
        assert(!fJitOptRepeatRange.Error());
        if (!fJitOptRepeatRange.IsEmpty() && fJitOptRepeatRange.Contains(info.compMethodHash()))
        {
            opts.optRepeat      = true;
            opts.optRepeatCount = JitConfig.JitOptRepeatCount();

            JITDUMP("\n*************** JitOptRepeat enabled by JitOptRepeatRange; repetition count: %d\n\n",
                    opts.optRepeatCount);
        }

        if (!opts.optRepeat && compStressCompile(STRESS_OPT_REPEAT, 10))
        {
            // Turn on optRepeat as part of JitStress. In this case, decide how many iterations to do, from 2 to 5,
            // based on a random number seeded by the method hash.
            opts.optRepeat = true;

            CLRRandom rng;
            rng.Init(info.compMethodHash());
            opts.optRepeatCount = rng.Next(4) + 2; // generates [2..5]

            JITDUMP("\n*************** JitOptRepeat for stress; repetition count: %d\n\n", opts.optRepeatCount);
        }
#endif // DEBUG
    }

#ifdef DEBUG
    assert(!codeGen->isGCTypeFixed());
    opts.compGcChecks = (JitConfig.JitGCChecks() != 0) || compStressCompile(STRESS_GENERIC_VARN, 5);
#endif

#if defined(DEBUG) && defined(TARGET_XARCH)
    enum
    {
        STACK_CHECK_ON_RETURN = 0x1,
        STACK_CHECK_ON_CALL   = 0x2,
        STACK_CHECK_ALL       = 0x3
    };

    DWORD dwJitStackChecks = JitConfig.JitStackChecks();
    if (compStressCompile(STRESS_GENERIC_VARN, 5))
    {
        dwJitStackChecks = STACK_CHECK_ALL;
    }
    opts.compStackCheckOnRet = (dwJitStackChecks & DWORD(STACK_CHECK_ON_RETURN)) != 0;
#if defined(TARGET_X86)
    opts.compStackCheckOnCall = (dwJitStackChecks & DWORD(STACK_CHECK_ON_CALL)) != 0;
#endif // defined(TARGET_X86)
#endif // defined(DEBUG) && defined(TARGET_XARCH)

#if MEASURE_MEM_ALLOC
    s_dspMemStats = (JitConfig.DisplayMemStats() != 0);
#endif

#ifdef PROFILING_SUPPORTED
    opts.compNoPInvokeInlineCB = jitFlags->IsSet(JitFlags::JIT_FLAG_PROF_NO_PINVOKE_INLINE);

    // Cache the profiler handle
    if (jitFlags->IsSet(JitFlags::JIT_FLAG_PROF_ENTERLEAVE))
    {
        bool hookNeeded;
        bool indirected;
        info.compCompHnd->GetProfilingHandle(&hookNeeded, &compProfilerMethHnd, &indirected);
        compProfilerHookNeeded        = !!hookNeeded;
        compProfilerMethHndIndirected = !!indirected;
    }
    else
    {
        compProfilerHookNeeded        = false;
        compProfilerMethHnd           = nullptr;
        compProfilerMethHndIndirected = false;
    }

    // Honour DOTNET_JitELTHookEnabled or STRESS_PROFILER_CALLBACKS stress mode
    // only if VM has not asked us to generate profiler hooks in the first place.
    // That is, override VM only if it hasn't asked for a profiler callback for this method.
    // Don't run this stress mode under AOT, as we would need to emit a relocation
    // for the call to the fake ELT hook, which wouldn't make sense, as we can't store that
    // in the AOT image.
    if (!compProfilerHookNeeded)
    {
        if ((JitConfig.JitELTHookEnabled() != 0) ||
            (!jitFlags->IsSet(JitFlags::JIT_FLAG_AOT) && compStressCompile(STRESS_PROFILER_CALLBACKS, 5)))
        {
            opts.compJitELTHookEnabled = true;
        }
    }

    // TBD: Exclude PInvoke stubs
    if (opts.compJitELTHookEnabled)
    {
#if defined(DEBUG) // We currently only know if we're running under SuperPMI in DEBUG
        // We don't want to get spurious SuperPMI asm diffs because profile stress kicks in and we use
        // the address of `DummyProfilerELTStub` in the JIT binary, without relocation. So just use
        // a fixed address in this case. It's SuperPMI replay, so the generated code won't be run.
        if (RunningSuperPmiReplay())
        {
#ifdef HOST_64BIT
            static_assert(sizeof(void*) == 8);
            compProfilerMethHnd = (void*)0x0BADF00DBEADCAFE;
#else
            static_assert(sizeof(void*) == 4);
            compProfilerMethHnd = (void*)0x0BADF00D;
#endif
        }
        else
#endif // DEBUG
        {
            compProfilerMethHnd = (void*)DummyProfilerELTStub;
        }
        compProfilerMethHndIndirected = false;
    }

#endif // PROFILING_SUPPORTED

#if FEATURE_TAILCALL_OPT
    const char* strTailCallOpt = JitConfig.TailCallOpt();
    if (strTailCallOpt != nullptr)
    {
        opts.compTailCallOpt = (UINT)atoi(strTailCallOpt) != 0;
    }

    if (JitConfig.TailCallLoopOpt() == 0)
    {
        opts.compTailCallLoopOpt = false;
    }
#endif

#if FEATURE_FASTTAILCALL
    if (JitConfig.FastTailCalls() == 0)
    {
        opts.compFastTailCalls = false;
    }
#endif // FEATURE_FASTTAILCALL

#ifdef CONFIGURABLE_ARM_ABI
    opts.compUseSoftFP        = jitFlags->IsSet(JitFlags::JIT_FLAG_SOFTFP_ABI);
    unsigned int softFPConfig = opts.compUseSoftFP ? 2 : 1;
    unsigned int oldSoftFPConfig =
        InterlockedCompareExchange(&GlobalJitOptions::compUseSoftFPConfigured, softFPConfig, 0);
    if (oldSoftFPConfig != softFPConfig && oldSoftFPConfig != 0)
    {
        // There are no current scenarios where the abi can change during the lifetime of a process
        // that uses the JIT. If such a change occurs, either compFeatureHfa will need to change to a TLS static
        // or we will need to have some means to reset the flag safely.
        NO_WAY("SoftFP ABI setting changed during lifetime of process");
    }

    GlobalJitOptions::compFeatureHfa = !opts.compUseSoftFP;
#elif defined(ARM_SOFTFP) && defined(TARGET_ARM)
    // Armel is unconditionally enabled in the JIT. Verify that the VM side agrees.
    assert(jitFlags->IsSet(JitFlags::JIT_FLAG_SOFTFP_ABI));
#elif defined(TARGET_ARM)
    assert(!jitFlags->IsSet(JitFlags::JIT_FLAG_SOFTFP_ABI));
#endif // CONFIGURABLE_ARM_ABI

    opts.compScopeInfo = opts.compDbgInfo;

#ifdef LATE_DISASM
    codeGen->getDisAssembler().disOpenForLateDisAsm(info.compMethodName, info.compClassName,
                                                    info.compMethodInfo->args.pSig);
#endif

    //-------------------------------------------------------------------------

    opts.compReloc = jitFlags->IsSet(JitFlags::JIT_FLAG_RELOC);

    bool enableFakeSplitting = false;

#ifdef DEBUG
    enableFakeSplitting = JitConfig.JitFakeProcedureSplitting();

#if defined(TARGET_XARCH) || defined(TARGET_RISCV64)
    // Whether encoding of absolute addr as PC-rel offset is enabled
    opts.compEnablePCRelAddr = (JitConfig.EnablePCRelAddr() != 0);
#endif
#endif // DEBUG

    opts.compProcedureSplitting = jitFlags->IsSet(JitFlags::JIT_FLAG_PROCSPLIT) || enableFakeSplitting;

#ifdef FEATURE_CFI_SUPPORT
    // Hot/cold splitting is not being tested on NativeAOT.
    if (generateCFIUnwindCodes())
    {
        opts.compProcedureSplitting = false;
    }
#endif // FEATURE_CFI_SUPPORT

#if defined(TARGET_LOONGARCH64) || defined(TARGET_RISCV64)
    opts.compProcedureSplitting = false;
#endif // TARGET_LOONGARCH64 || TARGET_RISCV64

#ifdef DEBUG
    opts.compProcedureSplittingEH = opts.compProcedureSplitting;
#endif // DEBUG

    if (opts.compProcedureSplitting)
    {
        // Note that opts.compDbgCode is true under AOT for checked assemblies!
        opts.compProcedureSplitting = !opts.compDbgCode || enableFakeSplitting;

#ifdef DEBUG
        // JitForceProcedureSplitting is used to force procedure splitting on checked assemblies.
        // This is useful for debugging on a checked build.  Note that we still only do procedure
        // splitting in the zapper.
        if (JitConfig.JitForceProcedureSplitting().contains(info.compMethodHnd, info.compClassHnd,
                                                            &info.compMethodInfo->args))
        {
            opts.compProcedureSplitting = true;
        }

        // JitNoProcedureSplitting will always disable procedure splitting.
        if (JitConfig.JitNoProcedureSplitting().contains(info.compMethodHnd, info.compClassHnd,
                                                         &info.compMethodInfo->args))
        {
            opts.compProcedureSplitting = false;
        }
        //
        // JitNoProcedureSplittingEH will disable procedure splitting in functions with EH.
        if (JitConfig.JitNoProcedureSplittingEH().contains(info.compMethodHnd, info.compClassHnd,
                                                           &info.compMethodInfo->args))
        {
            opts.compProcedureSplittingEH = false;
        }
#endif
    }

#ifdef TARGET_64BIT
    opts.compCollect64BitCounts = JitConfig.JitCollect64BitCounts() != 0;

#ifdef DEBUG
    if (JitConfig.JitRandomlyCollect64BitCounts() != 0)
    {
        CLRRandom rng;
        rng.Init(info.compMethodHash() ^ JitConfig.JitRandomlyCollect64BitCounts() ^ 0x3485e20e);
        opts.compCollect64BitCounts = rng.Next(2) == 0;
    }
#endif
#else
    opts.compCollect64BitCounts = false;
#endif

#ifdef DEBUG

    // Now, set compMaxUncheckedOffsetForNullObject for STRESS_NULL_OBJECT_CHECK
    if (compStressCompile(STRESS_NULL_OBJECT_CHECK, 30))
    {
        compMaxUncheckedOffsetForNullObject = (size_t)JitConfig.JitMaxUncheckedOffset();
        if (verbose)
        {
            printf("STRESS_NULL_OBJECT_CHECK: compMaxUncheckedOffsetForNullObject=0x%zX\n",
                   compMaxUncheckedOffsetForNullObject);
        }
    }

    if (verbose)
    {
        // If we are compiling for a specific tier, make that very obvious in the output.
        // Note that we don't expect multiple TIER flags to be set at one time, but there
        // is nothing preventing that.
        if (jitFlags->IsSet(JitFlags::JIT_FLAG_TIER0))
        {
            printf("OPTIONS: Tier-0 compilation (set DOTNET_TieredCompilation=0 to disable)\n");
        }
        if (jitFlags->IsSet(JitFlags::JIT_FLAG_TIER1))
        {
            printf("OPTIONS: Tier-1 compilation\n");
        }
        if (compSwitchedToOptimized)
        {
            printf("OPTIONS: Tier-0 compilation, switched to FullOpts\n");
        }
        if (compSwitchedToMinOpts)
        {
            printf("OPTIONS: Tier-1/FullOpts compilation, switched to MinOpts\n");
        }

        if (jitFlags->IsSet(JitFlags::JIT_FLAG_OSR))
        {
            printf("OPTIONS: OSR variant with entry point 0x%x\n", info.compILEntry);
        }

        printf("OPTIONS: compCodeOpt = %s\n", (opts.compCodeOpt == BLENDED_CODE) ? "BLENDED_CODE"
                                              : (opts.compCodeOpt == SMALL_CODE) ? "SMALL_CODE"
                                              : (opts.compCodeOpt == FAST_CODE)  ? "FAST_CODE"
                                                                                 : "UNKNOWN_CODE");

        printf("OPTIONS: compDbgCode = %s\n", dspBool(opts.compDbgCode));
        printf("OPTIONS: compDbgInfo = %s\n", dspBool(opts.compDbgInfo));
        printf("OPTIONS: compDbgEnC  = %s\n", dspBool(opts.compDbgEnC));
        printf("OPTIONS: compProcedureSplitting   = %s\n", dspBool(opts.compProcedureSplitting));
        printf("OPTIONS: compProcedureSplittingEH = %s\n", dspBool(opts.compProcedureSplittingEH));

        // This is rare; don't clutter up the dump with it normally.
        if (compProfilerHookNeeded)
        {
            printf("OPTIONS: compProfilerHookNeeded   = %s\n", dspBool(compProfilerHookNeeded));
        }

        if (jitFlags->IsSet(JitFlags::JIT_FLAG_BBOPT))
        {
            printf("OPTIONS: optimizer should use profile data\n");
        }

        if (jitFlags->IsSet(JitFlags::JIT_FLAG_AOT))
        {
            printf("OPTIONS: Jit invoked for AOT\n");
        }

        if (compIsAsync())
        {
            printf("OPTIONS: compilation is an async state machine\n");
        }
    }
#endif

#ifdef PROFILING_SUPPORTED
#ifdef UNIX_AMD64_ABI
    if (compIsProfilerHookNeeded())
    {
        opts.compNeedToAlignFrame = true;
    }
#endif // UNIX_AMD64_ABI
#endif

#if defined(DEBUG) && defined(TARGET_ARM64)
    if ((s_pJitMethodSet == nullptr) || s_pJitMethodSet->IsActiveMethod(info.compFullName, info.compMethodHash()))
    {
        opts.compJitSaveFpLrWithCalleeSavedRegisters = JitConfig.JitSaveFpLrWithCalleeSavedRegisters();
    }
#endif // defined(DEBUG) && defined(TARGET_ARM64)

#if defined(TARGET_AMD64)
    rbmAllFloat         = RBM_ALLFLOAT_INIT;
    rbmFltCalleeTrash   = RBM_FLT_CALLEE_TRASH_INIT;
    cntCalleeTrashFloat = CNT_CALLEE_TRASH_FLOAT_INIT;

    rbmAllInt         = RBM_ALLINT_INIT;
    rbmIntCalleeTrash = RBM_INT_CALLEE_TRASH_INIT;
    cntCalleeTrashInt = CNT_CALLEE_TRASH_INT_INIT;
    regIntLast        = REG_R15;

    if (canUseEvexEncoding())
    {
        rbmAllFloat |= RBM_HIGHFLOAT;
        rbmFltCalleeTrash |= RBM_HIGHFLOAT;
        cntCalleeTrashFloat += CNT_CALLEE_TRASH_HIGHFLOAT;
    }

    if (canUseApxEncoding())
    {
        rbmAllInt |= RBM_HIGHINT;
        rbmIntCalleeTrash |= RBM_HIGHINT;
        cntCalleeTrashInt += CNT_CALLEE_TRASH_HIGHINT;
        regIntLast = REG_R31;
    }
#endif // TARGET_AMD64

#if defined(TARGET_XARCH)
    rbmAllMask         = RBM_ALLMASK_INIT;
    rbmMskCalleeTrash  = RBM_MSK_CALLEE_TRASH_INIT;
    cntCalleeTrashMask = CNT_CALLEE_TRASH_MASK_INIT;

    if (canUseEvexEncoding())
    {
        rbmAllMask |= RBM_ALLMASK_EVEX;
        rbmMskCalleeTrash |= RBM_MSK_CALLEE_TRASH_EVEX;
        cntCalleeTrashMask += CNT_CALLEE_TRASH_MASK_EVEX;
    }

    // Make sure we copy the register info and initialize the
    // trash regs after the underlying fields are initialized

    const regMaskTP vtCalleeTrashRegs[TYP_COUNT]{
#define DEF_TP(tn, nm, jitType, sz, sze, asze, st, al, regTyp, regFld, csr, ctr, tf) ctr,
#include "typelist.h"
#undef DEF_TP
    };
    memcpy(varTypeCalleeTrashRegs, vtCalleeTrashRegs, sizeof(regMaskTP) * TYP_COUNT);

    codeGen->CopyRegisterInfo();
#endif // TARGET_XARCH
}

#ifdef DEBUG

bool Compiler::compJitHaltMethod()
{
    // This method returns true when we use an INS_BREAKPOINT to allow us to step into the generated native code.
    // Note that these two "Jit" environment variables also work for AOT images.

    if (JitConfig.JitHalt().contains(info.compMethodHnd, info.compClassHnd, &info.compMethodInfo->args))
    {
        return true;
    }

    // Use this Hash variant when there are a lot of method with the same name and different signatures.

    unsigned fJitHashHaltVal = (unsigned)JitConfig.JitHashHalt();
    if ((fJitHashHaltVal != (unsigned)-1) && (fJitHashHaltVal == info.compMethodHash()))
    {
        return true;
    }

    return false;
}

/*****************************************************************************
 * Should we use a "stress-mode" for the given stressArea. We have different
 *   areas to allow the areas to be mixed in different combinations in
 *   different methods.
 * 'weight' indicates how often (as a percentage) the area should be stressed.
 *    It should reflect the usefulness:overhead ratio.
 */

const char* Compiler::s_compStressModeNames[STRESS_COUNT + 1] = {
#define STRESS_MODE(mode) "STRESS_" #mode,

    STRESS_MODES
#undef STRESS_MODE
};

//------------------------------------------------------------------------
// compStressCompile: determine if a stress mode should be enabled
//
// Arguments:
//   stressArea - stress mode to possibly enable
//   weight - percent of time this mode should be turned on
//     (range 0 to 100); weight 0 effectively disables
//
// Returns:
//   true if this stress mode is enabled
//
// Notes:
//   Methods may be excluded from stress via name or hash.
//
//   Particular stress modes may be disabled or forcibly enabled.
//
//   With JitStress=2, some stress modes are enabled regardless of weight;
//   these modes are the ones after COUNT_VARN in the enumeration.
//
//   For other modes or for nonzero JitStress values, stress will be
//   enabled selectively for roughly weight% of methods.
//
bool Compiler::compStressCompile(compStressArea stressArea, unsigned weight)
{
    // This can be called early, before info is fully set up.
    if ((info.compMethodName == nullptr) || (info.compFullName == nullptr))
    {
        return false;
    }

    // Inlinees defer to the root method for stress, so that we can
    // more easily isolate methods that cause stress failures.
    if (compIsForInlining())
    {
        return impInlineRoot()->compStressCompile(stressArea, weight);
    }

    const bool doStress = compStressCompileHelper(stressArea, weight);

    if (doStress && !compActiveStressModes[stressArea])
    {
        if (verbose)
        {
            printf("\n\n*** JitStress: %s ***\n\n", s_compStressModeNames[stressArea]);
        }
        compActiveStressModes[stressArea] = 1;
    }

    return doStress;
}

//------------------------------------------------------------------------
// compStressAreaHash: Get (or compute) a hash code for a stress area.
//
// Arguments:
//   stressArea - stress mode
//
// Returns:
//   A hash code for the specific stress area.
//
unsigned Compiler::compStressAreaHash(compStressArea area)
{
    static LONG s_hashCodes[STRESS_COUNT];
    assert(static_cast<unsigned>(area) < ArrLen(s_hashCodes));

    unsigned result = (unsigned)s_hashCodes[area];
    if (result == 0)
    {
        result = HashStringA(s_compStressModeNames[area]);
        if (result == 0)
        {
            result = 1;
        }

        InterlockedExchange(&s_hashCodes[area], (LONG)result);
    }

    return result;
}

//------------------------------------------------------------------------
// compStressCompileHelper: helper to determine if a stress mode should be enabled
//
// Arguments:
//   stressArea - stress mode to possibly enable
//   weight - percent of time this mode should be turned on
//     (range 0 to 100); weight 0 effectively disables
//
// Returns:
//   true if this stress mode is enabled
//
// Notes:
//   See compStressCompile
//
bool Compiler::compStressCompileHelper(compStressArea stressArea, unsigned weight)
{
    if (!compAllowStress)
    {
        return false;
    }

    // Does user explicitly prevent using this STRESS_MODE through the command line?
    const char* strStressModeNamesNot = JitConfig.JitStressModeNamesNot();
    if ((strStressModeNamesNot != nullptr) &&
        (strstr(strStressModeNamesNot, s_compStressModeNames[stressArea]) != nullptr))
    {
        return false;
    }

    // Does user allow using this STRESS_MODE through the command line?
    const char* strStressModeNamesAllow = JitConfig.JitStressModeNamesAllow();
    if ((strStressModeNamesAllow != nullptr) &&
        (strstr(strStressModeNamesAllow, s_compStressModeNames[stressArea]) == nullptr))
    {
        return false;
    }

    // Does user explicitly set this STRESS_MODE through the command line?
    const char* strStressModeNames = JitConfig.JitStressModeNames();
    if (strStressModeNames != nullptr)
    {
        if (strstr(strStressModeNames, s_compStressModeNames[stressArea]) != nullptr)
        {
            return true;
        }

        // This stress mode name did not match anything in the stress
        // mode allowlist. If user has requested only enable mode,
        // don't allow this stress mode to turn on.
        const bool onlyEnableMode = JitConfig.JitStressModeNamesOnly() != 0;

        if (onlyEnableMode)
        {
            return false;
        }
    }

    // 0:   No stress (Except when explicitly set in DOTNET_JitStressModeNames)
    // !=2: Vary stress. Performance will be slightly/moderately degraded
    // 2:   Check-all stress. Performance will be REALLY horrible
    const int stressLevel = getJitStressLevel();

    assert(weight <= MAX_STRESS_WEIGHT);

    // Check for boundary conditions
    if (stressLevel == 0 || weight == 0)
    {
        return false;
    }

    // Should we allow unlimited stress ?
    if ((stressArea > STRESS_COUNT_VARN) && (stressLevel == 2))
    {
        return true;
    }

    if (weight == MAX_STRESS_WEIGHT)
    {
        return true;
    }

    // Get a hash which can be compared with 'weight'
    assert(stressArea != 0);
    const unsigned hash = (info.compMethodHash() ^ compStressAreaHash(stressArea) ^ stressLevel) % MAX_STRESS_WEIGHT;

    assert(hash < MAX_STRESS_WEIGHT && weight <= MAX_STRESS_WEIGHT);
    return (hash < weight);
}

//------------------------------------------------------------------------
// compPromoteFewerStructs: helper to determine if the local
//   should not be promoted under a stress mode.
//
// Arguments:
//   lclNum - local number to test
//
// Returns:
//   true if this local should not be promoted.
//
// Notes:
//   Reject ~50% of the potential promotions if STRESS_PROMOTE_FEWER_STRUCTS is active.
//
bool Compiler::compPromoteFewerStructs(unsigned lclNum)
{
    bool       rejectThisPromo = false;
    const bool promoteLess     = compStressCompile(STRESS_PROMOTE_FEWER_STRUCTS, 50);
    if (promoteLess)
    {

        rejectThisPromo = (((info.compMethodHash() ^ lclNum) & 1) == 0);
    }
    return rejectThisPromo;
}

//------------------------------------------------------------------------
// dumpRegMask: display a register mask. For well-known sets of registers, display a well-known token instead of
// a potentially large number of registers.
//
// Arguments:
//   regs - The set of registers to display
//   type - The type of `regs`
//
void Compiler::dumpRegMask(SingleTypeRegSet regs, var_types type) const
{
#ifdef FEATURE_MASKED_HW_INTRINSICS
    if (varTypeIsMask(type))
    {
        dumpRegMask(regMaskTP(RBM_NONE, regs));
    }
    else
#endif
    {
        assert(varTypeUsesIntReg(type) || varTypeUsesFloatReg(type));
        dumpRegMask(regMaskTP(regs, RBM_NONE));
    }
}

//------------------------------------------------------------------------
// dumpRegMask: display a register mask. For well-known sets of registers, display a well-known token instead of
// a potentially large number of registers.
//
// Arguments:
//   regs - The set of registers to display
//
void Compiler::dumpRegMask(regMaskTP regs) const
{
    if (regs == RBM_ALLINT)
    {
        printf("[allInt]");
    }
    else if (regs == (RBM_ALLINT & ~RBM_FPBASE))
    {
        printf("[allIntButFP]");
    }
    else if (regs == RBM_ALLFLOAT)
    {
        printf("[allFloat]");
    }
    else if (regs == RBM_ALLDOUBLE)
    {
        printf("[allDouble]");
    }
#ifdef FEATURE_MASKED_HW_INTRINSICS
    else if (regs == RBM_ALLMASK)
    {
        printf("[allMask]");
    }
#endif // TARGET_XARCH
    else
    {
        dspRegMask(regs);
    }
}

#endif // DEBUG

void Compiler::compInitDebuggingInfo()
{
#ifdef DEBUG
    if (verbose)
    {
        printf("*************** In compInitDebuggingInfo() for %s\n", info.compFullName);
    }
#endif

    /*-------------------------------------------------------------------------
     *
     * Get hold of the local variable records, if there are any
     */

    info.compVarScopesCount = 0;

    if (opts.compScopeInfo)
    {
        eeGetVars();
    }

    compInitVarScopeMap();

    if (opts.compScopeInfo || opts.compDbgCode)
    {
        compInitScopeLists();
    }

    /*-------------------------------------------------------------------------
     *
     * Read the stmt-offsets table and the line-number table
     */

    info.compStmtOffsetsImplicit = ICorDebugInfo::NO_BOUNDARIES;

    // We can only report debug info for EnC at places where the stack is empty.
    // Actually, at places where there are not live temps. Else, we won't be able
    // to map between the old and the new versions correctly as we won't have
    // any info for the live temps.

    assert(!opts.compDbgEnC || !opts.compDbgInfo ||
           0 == (info.compStmtOffsetsImplicit & ~ICorDebugInfo::STACK_EMPTY_BOUNDARIES));

    info.compStmtOffsetsCount = 0;

    if (opts.compDbgInfo)
    {
        /* Get hold of the line# records, if there are any */

        eeGetStmtOffsets();

#ifdef DEBUG
        if (verbose)
        {
            printf("info.compStmtOffsetsCount    = %d\n", info.compStmtOffsetsCount);
            printf("info.compStmtOffsetsImplicit = %04Xh", info.compStmtOffsetsImplicit);

            if (info.compStmtOffsetsImplicit)
            {
                printf(" ( ");
                if (info.compStmtOffsetsImplicit & ICorDebugInfo::STACK_EMPTY_BOUNDARIES)
                {
                    printf("STACK_EMPTY ");
                }
                if (info.compStmtOffsetsImplicit & ICorDebugInfo::NOP_BOUNDARIES)
                {
                    printf("NOP ");
                }
                if (info.compStmtOffsetsImplicit & ICorDebugInfo::CALL_SITE_BOUNDARIES)
                {
                    printf("CALL_SITE ");
                }
                printf(")");
            }
            printf("\n");
            IL_OFFSET* pOffs = info.compStmtOffsets;
            for (unsigned i = 0; i < info.compStmtOffsetsCount; i++, pOffs++)
            {
                printf("%02d) IL_%04Xh\n", i, *pOffs);
            }
        }
#endif
    }
}

void Compiler::compSetOptimizationLevel()
{
    bool theMinOptsValue;
#pragma warning(suppress : 4101)
    unsigned jitMinOpts;

    if (compIsForInlining())
    {
        theMinOptsValue = impInlineInfo->InlinerCompiler->opts.MinOpts();
        goto _SetMinOpts;
    }

    theMinOptsValue = false;

    if (opts.compFlags == CLFLG_MINOPT)
    {
        JITLOG((LL_INFO100, "CLFLG_MINOPT set for method %s\n", info.compFullName));
        theMinOptsValue = true;
    }

#ifdef DEBUG
    jitMinOpts = JitConfig.JitMinOpts();

    if (!theMinOptsValue && (jitMinOpts > 0))
    {
        // jitTotalMethodCompiled does not include the method that is being compiled now, so make +1.
        unsigned methodCount     = Compiler::jitTotalMethodCompiled + 1;
        unsigned methodCountMask = methodCount & 0xFFF;
        unsigned kind            = (jitMinOpts & 0xF000000) >> 24;
        switch (kind)
        {
            default:
                if (jitMinOpts <= methodCount)
                {
                    if (verbose)
                    {
                        printf(" Optimizations disabled by JitMinOpts and methodCount\n");
                    }
                    theMinOptsValue = true;
                }
                break;
            case 0xD:
            {
                unsigned firstMinopts  = (jitMinOpts >> 12) & 0xFFF;
                unsigned secondMinopts = (jitMinOpts >> 0) & 0xFFF;

                if ((firstMinopts == methodCountMask) || (secondMinopts == methodCountMask))
                {
                    if (verbose)
                    {
                        printf("0xD: Optimizations disabled by JitMinOpts and methodCountMask\n");
                    }
                    theMinOptsValue = true;
                }
            }
            break;
            case 0xE:
            {
                unsigned startMinopts = (jitMinOpts >> 12) & 0xFFF;
                unsigned endMinopts   = (jitMinOpts >> 0) & 0xFFF;

                if ((startMinopts <= methodCountMask) && (endMinopts >= methodCountMask))
                {
                    if (verbose)
                    {
                        printf("0xE: Optimizations disabled by JitMinOpts and methodCountMask\n");
                    }
                    theMinOptsValue = true;
                }
            }
            break;
            case 0xF:
            {
                unsigned bitsZero = (jitMinOpts >> 12) & 0xFFF;
                unsigned bitsOne  = (jitMinOpts >> 0) & 0xFFF;

                if (((methodCountMask & bitsOne) == bitsOne) && ((~methodCountMask & bitsZero) == bitsZero))
                {
                    if (verbose)
                    {
                        printf("0xF: Optimizations disabled by JitMinOpts and methodCountMask\n");
                    }
                    theMinOptsValue = true;
                }
            }
            break;
        }
    }

    if (!theMinOptsValue)
    {
        if (JitConfig.JitMinOptsName().contains(info.compMethodHnd, info.compClassHnd, &info.compMethodInfo->args))
        {
            theMinOptsValue = true;
        }
    }

#ifdef DEBUG
    static ConfigMethodRange s_onlyOptimizeRange;
    s_onlyOptimizeRange.EnsureInit(JitConfig.JitOnlyOptimizeRange());

    if (!theMinOptsValue && !s_onlyOptimizeRange.IsEmpty())
    {
        unsigned methHash = info.compMethodHash();
        theMinOptsValue   = !s_onlyOptimizeRange.Contains(methHash);
    }
#endif

    if (compStressCompile(STRESS_MIN_OPTS, 5))
    {
        theMinOptsValue = true;
    }
    // For AOT we never drop down to MinOpts unless unless CLFLG_MINOPT is set
    else if (!IsAot())
    {
        if ((unsigned)JitConfig.JitMinOptsCodeSize() < info.compILCodeSize)
        {
            JITLOG((LL_INFO10, "IL Code Size exceeded, using MinOpts for method %s\n", info.compFullName));
            theMinOptsValue = true;
        }
        else if ((unsigned)JitConfig.JitMinOptsInstrCount() < opts.instrCount)
        {
            JITLOG((LL_INFO10, "IL instruction count exceeded, using MinOpts for method %s\n", info.compFullName));
            theMinOptsValue = true;
        }
        else if ((unsigned)JitConfig.JitMinOptsBbCount() < fgBBcount)
        {
            JITLOG((LL_INFO10, "Basic Block count exceeded, using MinOpts for method %s\n", info.compFullName));
            theMinOptsValue = true;
        }
        else if ((unsigned)JitConfig.JitMinOptsLvNumCount() < lvaCount)
        {
            JITLOG((LL_INFO10, "Local Variable Num count exceeded, using MinOpts for method %s\n", info.compFullName));
            theMinOptsValue = true;
        }
        else if ((unsigned)JitConfig.JitMinOptsLvRefCount() < opts.lvRefCount)
        {
            JITLOG((LL_INFO10, "Local Variable Ref count exceeded, using MinOpts for method %s\n", info.compFullName));
            theMinOptsValue = true;
        }
        if (theMinOptsValue == true)
        {
            JITLOG((LL_INFO10000,
                    "IL Code Size,Instr %4d,%4d, Basic Block count %3d, Local Variable Num,Ref count "
                    "%3d,%3d for method %s\n",
                    info.compILCodeSize, opts.instrCount, fgBBcount, lvaCount, opts.lvRefCount, info.compFullName));
            if (JitConfig.JitBreakOnMinOpts() != 0)
            {
                assert(!"MinOpts enabled");
            }
        }
    }
#else  // !DEBUG
    // Retail check if we should force Minopts due to the complexity of the method.
    // For AOT we never drop down to MinOpts unless unless CLFLG_MINOPT is set.
    if (!theMinOptsValue && !IsAot() &&
        ((DEFAULT_MIN_OPTS_CODE_SIZE < info.compILCodeSize) || (DEFAULT_MIN_OPTS_INSTR_COUNT < opts.instrCount) ||
         (DEFAULT_MIN_OPTS_BB_COUNT < fgBBcount) || (DEFAULT_MIN_OPTS_LV_NUM_COUNT < lvaCount) ||
         (DEFAULT_MIN_OPTS_LV_REF_COUNT < opts.lvRefCount)))
    {
        theMinOptsValue = true;
    }
#endif // DEBUG

    JITLOG((LL_INFO10000,
            "IL Code Size,Instr %4d,%4d, Basic Block count %3d, Local Variable Num,Ref count %3d,%3d for method %s\n",
            info.compILCodeSize, opts.instrCount, fgBBcount, lvaCount, opts.lvRefCount, info.compFullName));

_SetMinOpts:

    // Set the MinOpts value
    opts.SetMinOpts(theMinOptsValue);

    // Notify the VM if MinOpts is being used when not requested
    if (theMinOptsValue && !compIsForInlining() && !opts.jitFlags->IsSet(JitFlags::JIT_FLAG_TIER0) &&
        !opts.jitFlags->IsSet(JitFlags::JIT_FLAG_MIN_OPT) && !opts.compDbgCode)
    {
        info.compCompHnd->setMethodAttribs(info.compMethodHnd, CORINFO_FLG_SWITCHED_TO_MIN_OPT);
        opts.jitFlags->Clear(JitFlags::JIT_FLAG_TIER1);
        opts.jitFlags->Clear(JitFlags::JIT_FLAG_BBOPT);
        compSwitchedToMinOpts = true;
    }

#ifdef DEBUG
    if (verbose && !compIsForInlining())
    {
        printf("OPTIONS: opts.MinOpts() == %s\n", opts.MinOpts() ? "true" : "false");
    }
#endif

    /* Control the optimizations */

    if (opts.OptimizationDisabled())
    {
        opts.compFlags &= ~CLFLG_MAXOPT;
        opts.compFlags |= CLFLG_MINOPT;

        lvaEnregEHVars &= compEnregLocals();
        lvaEnregMultiRegVars &= compEnregLocals();

        // Scrub any profile data we might have fetched
        //
        fgRemoveProfileData("compiling with minopt");
    }

    if (!compIsForInlining())
    {
        codeGen->setFramePointerRequired(false);
        codeGen->setFrameRequired(false);

        if (opts.OptimizationDisabled())
        {
            codeGen->setFrameRequired(true);
        }

#if !defined(TARGET_AMD64)
        // The VM sets JitFlags::JIT_FLAG_FRAMED for two reasons: (1) the DOTNET_JitFramed variable is set, or
        // (2) the function is marked "noinline". The reason for #2 is that people mark functions
        // noinline to ensure the show up on in a stack walk. But for AMD64, we don't need a frame
        // pointer for the frame to show up in stack walk.
        if (opts.jitFlags->IsSet(JitFlags::JIT_FLAG_FRAMED))
            codeGen->setFrameRequired(true);
#endif

        if (opts.OptimizationDisabled() || IsReadyToRun())
        {
            // The JIT doesn't currently support loop alignment for AOT images outside NativeAOT.
            // (The JIT doesn't know the final address of the code, hence
            // it can't align code based on unknown addresses.)

            codeGen->SetAlignLoops(false); // loop alignment not supported for AOT code
        }
        else
        {
            codeGen->SetAlignLoops(JitConfig.JitAlignLoops() == 1);
        }

#ifdef DEBUG
        const char* tieringName = compGetTieringName(true);
        JitMetadata::report(this, JitMetadata::TieringName, tieringName, strlen(tieringName));
#endif
    }
}

#if defined(TARGET_ARMARCH) || defined(TARGET_RISCV64)
// Function compRsvdRegCheck:
//  given a curState to use for calculating the total frame size
//  it will return true if the REG_OPT_RSVD should be reserved so
//  that it can be use to form large offsets when accessing stack
//  based LclVar including both incoming and out going argument areas.
//
//  The method advances the frame layout state to curState by calling
//  lvaFrameSize(curState).
//
bool Compiler::compRsvdRegCheck(FrameLayoutState curState)
{
    // Always do the layout even if returning early. Callers might
    // depend on us to do the layout.
    unsigned frameSize = lvaFrameSize(curState);
    JITDUMP("\n"
            "compRsvdRegCheck\n"
            "  frame size  = %6d\n"
            "  lvaParameterStackSize = %6d\n",
            frameSize, lvaParameterStackSize);

    if (opts.MinOpts())
    {
        // Have a recovery path in case we fail to reserve REG_OPT_RSVD and go
        // over the limit of SP and FP offset ranges due to large
        // temps.
        JITDUMP(" Returning true (MinOpts)\n\n");
        return true;
    }

    unsigned calleeSavedRegMaxSz = CALLEE_SAVED_REG_MAXSZ;
    if (compFloatingPointUsed)
    {
        calleeSavedRegMaxSz += CALLEE_SAVED_FLOAT_MAXSZ;
    }
    calleeSavedRegMaxSz += REGSIZE_BYTES; // we always push LR.  See genPushCalleeSavedRegisters

    noway_assert(frameSize >= calleeSavedRegMaxSz);

#if defined(TARGET_ARM64)

    // TODO-ARM64-CQ: update this!
    JITDUMP(" Returning true (ARM64)\n\n");
    return true; // just always assume we'll need it, for now

#elif defined(TARGET_RISCV64)
    JITDUMP(" Returning true (RISCV64)\n\n");
    return true; // just always assume we'll need it, for now

#else  // TARGET_ARM

    // frame layout:
    //
    //         ... high addresses ...
    //                         frame contents       size
    //                         -------------------  ------------------------
    //                         inArgs               compArgSize (includes prespill)
    //  caller SP --->
    //                         prespill
    //                         LR                   REGSIZE_BYTES
    //  R11    --->            R11                  REGSIZE_BYTES
    //                         callee saved regs    CALLEE_SAVED_REG_MAXSZ   (32 bytes)
    //                     optional saved fp regs   CALLEE_SAVED_FLOAT_MAXSZ (64 bytes)
    //                         lclSize
    //                             incl. TEMPS      MAX_SPILL_TEMP_SIZE
    //                             incl. outArgs
    //  SP     --->
    //          ... low addresses ...
    //
    // When codeGen->isFramePointerRequired is true, R11 will be established as a frame pointer.
    // We can then use R11 to access incoming args with positive offsets, and LclVars with
    // negative offsets.
    //
    // In functions with EH, in the non-funclet (or main) region, even though we will have a
    // frame pointer, we can use SP with positive offsets to access any or all locals or arguments
    // that we can reach with SP-relative encodings. The funclet region might require the reserved
    // register, since it must use offsets from R11 to access the parent frame.

    unsigned maxR11PositiveEncodingOffset = compFloatingPointUsed ? 0x03FC : 0x0FFF;
    JITDUMP("  maxR11PositiveEncodingOffset     = %6d\n", maxR11PositiveEncodingOffset);

    // Floating point load/store instructions (VLDR/VSTR) can address up to -0x3FC from R11, but we
    // don't know if there are either no integer locals, or if we don't need large negative offsets
    // for the integer locals, so we must use the integer max negative offset, which is a
    // smaller (absolute value) number.
    unsigned maxR11NegativeEncodingOffset = 0x00FF; // This is a negative offset from R11.
    JITDUMP("  maxR11NegativeEncodingOffset     = %6d\n", maxR11NegativeEncodingOffset);

    // -1 because otherwise we are computing the address just beyond the last argument, which we don't need to do.
    unsigned maxR11PositiveOffset = lvaParameterStackSize + (2 * REGSIZE_BYTES) - 1;
    JITDUMP("  maxR11PositiveOffset             = %6d\n", maxR11PositiveOffset);

    // The value is positive, but represents a negative offset from R11.
    // frameSize includes callee-saved space for R11 and LR, which are at non-negative offsets from R11
    // (+0 and +4, respectively), so don't include those in the max possible negative offset.
    assert(frameSize >= (2 * REGSIZE_BYTES));
    unsigned maxR11NegativeOffset = frameSize - (2 * REGSIZE_BYTES);
    JITDUMP("  maxR11NegativeOffset             = %6d\n", maxR11NegativeOffset);

    if (codeGen->isFramePointerRequired())
    {
        if (maxR11NegativeOffset > maxR11NegativeEncodingOffset)
        {
            JITDUMP(" Returning true (frame required and maxR11NegativeOffset)\n\n");
            return true;
        }
        if (maxR11PositiveOffset > maxR11PositiveEncodingOffset)
        {
            JITDUMP(" Returning true (frame required and maxR11PositiveOffset)\n\n");
            return true;
        }
    }

    // Now consider the SP based frame case. Note that we will use SP based offsets to access the stack in R11 based
    // frames in the non-funclet main code area.

    unsigned maxSPPositiveEncodingOffset = compFloatingPointUsed ? 0x03FC : 0x0FFF;
    JITDUMP("  maxSPPositiveEncodingOffset      = %6d\n", maxSPPositiveEncodingOffset);

    // -1 because otherwise we are computing the address just beyond the last argument, which we don't need to do.
    assert(lvaParameterStackSize + frameSize > 0);
    unsigned maxSPPositiveOffset = lvaParameterStackSize + frameSize - 1;

    if (codeGen->isFramePointerUsed())
    {
        // We have a frame pointer, so we can use it to access part of the stack, even if SP can't reach those parts.
        // We will still generate SP-relative offsets if SP can reach.

        // First, check that the stack between R11 and SP can be fully reached, either via negative offset from FP
        // or positive offset from SP. Don't count stored R11 or LR, which are reached from positive offsets from FP.

        unsigned maxSPLocalsCombinedOffset = frameSize - (2 * REGSIZE_BYTES) - 1;
        JITDUMP("  maxSPLocalsCombinedOffset        = %6d\n", maxSPLocalsCombinedOffset);

        if (maxSPLocalsCombinedOffset > maxSPPositiveEncodingOffset)
        {
            // Can R11 help?
            unsigned maxRemainingLocalsCombinedOffset = maxSPLocalsCombinedOffset - maxSPPositiveEncodingOffset;
            JITDUMP("  maxRemainingLocalsCombinedOffset = %6d\n", maxRemainingLocalsCombinedOffset);

            if (maxRemainingLocalsCombinedOffset > maxR11NegativeEncodingOffset)
            {
                JITDUMP(" Returning true (frame pointer exists; R11 and SP can't reach entire stack between them)\n\n");
                return true;
            }

            // Otherwise, yes, we can address the remaining parts of the locals frame with negative offsets from R11.
        }

        // Check whether either R11 or SP can access the arguments.
        if ((maxR11PositiveOffset > maxR11PositiveEncodingOffset) &&
            (maxSPPositiveOffset > maxSPPositiveEncodingOffset))
        {
            JITDUMP(" Returning true (frame pointer exists; R11 and SP can't reach all arguments)\n\n");
            return true;
        }
    }
    else
    {
        if (maxSPPositiveOffset > maxSPPositiveEncodingOffset)
        {
            JITDUMP(" Returning true (no frame pointer exists; SP can't reach all of frame)\n\n");
            return true;
        }
    }

    // We won't need to reserve REG_OPT_RSVD.
    //
    JITDUMP(" Returning false\n\n");
    return false;
#endif // TARGET_ARM
}
#endif // TARGET_ARMARCH || TARGET_RISCV64

//------------------------------------------------------------------------
// FindParameterRegisterLocalMappingByRegister:
//   Try to find a mapping that maps a particular parameter register to an
//   incoming defined local.
//
// Returns:
//   The mapping, or nullptr if no mapping was found for this register.
//
const ParameterRegisterLocalMapping* Compiler::FindParameterRegisterLocalMappingByRegister(regNumber reg)
{
    if (m_paramRegLocalMappings == nullptr)
    {
        return nullptr;
    }

    for (int i = 0; i < m_paramRegLocalMappings->Height(); i++)
    {
        const ParameterRegisterLocalMapping& mapping = m_paramRegLocalMappings->BottomRef(i);
        if (mapping.RegisterSegment->GetRegister() == reg)
        {
            return &mapping;
        }
    }

    return nullptr;
}

//------------------------------------------------------------------------
// FindParameterRegisterLocalMappingByLocal:
//   Try to find a mapping that maps a particular local from an incoming
//   parameter register.
//
// Parameters:
//   lclNum - The local to find a mapping for
//   offset - The offset that the mapping maps to in the local
//
// Returns:
//   The mapping, or nullptr if no mapping was found for this local.
//
const ParameterRegisterLocalMapping* Compiler::FindParameterRegisterLocalMappingByLocal(unsigned lclNum,
                                                                                        unsigned offset)
{
    if (m_paramRegLocalMappings == nullptr)
    {
        return nullptr;
    }

    for (int i = 0; i < m_paramRegLocalMappings->Height(); i++)
    {
        const ParameterRegisterLocalMapping& mapping = m_paramRegLocalMappings->BottomRef(i);
        if ((mapping.LclNum == lclNum) && (mapping.Offset == offset))
        {
            return &mapping;
        }
    }

    return nullptr;
}

//------------------------------------------------------------------------
// compGetTieringName: get a string describing tiered compilation settings
//   for this method
//
// Arguments:
//   wantShortName - true if a short name is ok (say for using in file names)
//
// Returns:
//   String describing tiering decisions for this method, including cases
//   where the jit codegen will differ from what the runtime requested.
//
const char* Compiler::compGetTieringName(bool wantShortName) const
{
    const bool tier0         = opts.jitFlags->IsSet(JitFlags::JIT_FLAG_TIER0);
    const bool tier1         = opts.jitFlags->IsSet(JitFlags::JIT_FLAG_TIER1);
    const bool instrumenting = opts.jitFlags->IsSet(JitFlags::JIT_FLAG_BBINSTR);

    if (!opts.compMinOptsIsSet)
    {
        // If 'compMinOptsIsSet' is not set, just return here. Otherwise, if this method is called
        // by the assertAbort(), we would recursively call assert while trying to get MinOpts()
        // and eventually stackoverflow.
        return "Optimization-Level-Not-Yet-Set";
    }

    assert(!tier0 || !tier1); // We don't expect multiple TIER flags to be set at one time.

    if (tier0)
    {
        return instrumenting ? "Instrumented Tier0" : "Tier0";
    }
    else if (tier1)
    {
        if (opts.IsOSR())
        {
            return instrumenting ? "Instrumented Tier1-OSR" : "Tier1-OSR";
        }
        else
        {
            return instrumenting ? "Instrumented Tier1" : "Tier1";
        }
    }
    else if (opts.OptimizationEnabled())
    {
        if (compSwitchedToOptimized)
        {
            return wantShortName ? "Tier0-FullOpts" : "Tier-0 switched to FullOpts";
        }
        else
        {
            return "FullOpts";
        }
    }
    else if (opts.MinOpts())
    {
        if (compSwitchedToMinOpts)
        {
            if (compSwitchedToOptimized)
            {
                return wantShortName ? "Tier0-FullOpts-MinOpts" : "Tier-0 switched to FullOpts, then to MinOpts";
            }
            else
            {
                return wantShortName ? "Tier0-MinOpts" : "Tier-0 switched MinOpts";
            }
        }
        else
        {
            return "MinOpts";
        }
    }
    else if (opts.compDbgCode)
    {
        return "Debug";
    }
    else
    {
        return wantShortName ? "Unknown" : "Unknown optimization level";
    }
}

//------------------------------------------------------------------------
// compGetPgoSourceName: get a string describing PGO source
//
// Returns:
//   String describing describing PGO source (e.g. Dynamic, Static, etc)
//
const char* Compiler::compGetPgoSourceName() const
{
    switch (fgPgoSource)
    {
        case ICorJitInfo::PgoSource::Static:
            return "Static PGO";
        case ICorJitInfo::PgoSource::Dynamic:
            return "Dynamic PGO";
        case ICorJitInfo::PgoSource::Blend:
            return "Blended PGO";
        case ICorJitInfo::PgoSource::Text:
            return "Textual PGO";
        case ICorJitInfo::PgoSource::Sampling:
            return "Sample-based PGO";
        case ICorJitInfo::PgoSource::IBC:
            return "Classic IBC";
        case ICorJitInfo::PgoSource::Synthesis:
            return "Synthesized PGO";
        default:
            return "Unknown PGO";
    }
}

//------------------------------------------------------------------------
// compGetStressMessage: get a string describing jitstress capability
//   for this method
//
// Returns:
//   An empty string if stress is not enabled, else a string describing
//   if this method is subject to stress or is excluded by name or hash.
//
const char* Compiler::compGetStressMessage() const
{
    // Add note about stress where appropriate
    const char* stressMessage = "";

#ifdef DEBUG
    // Is stress enabled via mode name or level?
    if ((JitConfig.JitStressModeNames() != nullptr) || (getJitStressLevel() > 0))
    {
        // Is the method being jitted excluded from stress via range?
        if (compAllowStress)
        {
            // Not excluded -- stress can happen
            stressMessage = " JitStress";
        }
        else
        {
            stressMessage = " NoJitStress";
        }
    }
#endif // DEBUG

    return stressMessage;
}

void Compiler::compFunctionTraceStart()
{
#ifdef DEBUG
    if (compIsForInlining())
    {
        return;
    }

    if ((JitConfig.JitFunctionTrace() != 0) && !opts.disDiffable)
    {
        LONG newJitNestingLevel = InterlockedIncrement(&Compiler::jitNestingLevel);
        if (newJitNestingLevel <= 0)
        {
            printf("{ Illegal nesting level %d }\n", newJitNestingLevel);
        }

        for (LONG i = 0; i < newJitNestingLevel - 1; i++)
        {
            printf("  ");
        }
        printf("{ Start Jitting Method %4d %s (MethodHash=%08x) %s\n", Compiler::jitTotalMethodCompiled,
               info.compFullName, info.compMethodHash(),
               compGetTieringName()); /* } editor brace matching workaround for this printf */
    }
#endif // DEBUG
}

void Compiler::compFunctionTraceEnd(void* methodCodePtr, ULONG methodCodeSize, bool isNYI)
{
#ifdef DEBUG
    assert(!compIsForInlining());

    if ((JitConfig.JitFunctionTrace() != 0) && !opts.disDiffable)
    {
        LONG newJitNestingLevel = InterlockedDecrement(&Compiler::jitNestingLevel);
        if (newJitNestingLevel < 0)
        {
            printf("{ Illegal nesting level %d }\n", newJitNestingLevel);
        }

        for (LONG i = 0; i < newJitNestingLevel; i++)
        {
            printf("  ");
        }

        // Note: that is incorrect if we are compiling several methods at the same time.
        unsigned methodNumber = Compiler::jitTotalMethodCompiled - 1;

        /* { editor brace-matching workaround for following printf */
        printf("} Jitted Method %4d at" FMT_ADDR "method %s size %08x%s%s\n", methodNumber, DBG_ADDR(methodCodePtr),
               info.compFullName, methodCodeSize, isNYI ? " NYI" : "", opts.altJit ? " altjit" : "");
    }
#endif // DEBUG
}

//------------------------------------------------------------------------
// BeginPhase: begin execution of a phase
//
// Arguments:
//    phase - the phase that is about to begin
//
void Compiler::BeginPhase(Phases phase)
{
    mostRecentlyActivePhase = phase;
}

//------------------------------------------------------------------------
// EndPhase: finish execution of a phase
//
// Arguments:
//    phase - the phase that has just finished
//
void Compiler::EndPhase(Phases phase)
{
#if defined(FEATURE_JIT_METHOD_PERF)
    if (pCompJitTimer != nullptr)
    {
        pCompJitTimer->EndPhase(this, phase);
    }
#endif

    mostRecentlyActivePhase = phase;
}

//------------------------------------------------------------------------
// compCompile: run phases needed for compilation
//
// Arguments:
//   methodCodePtr [OUT] - address of generated code
//   methodCodeSize [OUT] - size of the generated code (hot + cold sections)
//   compileFlags [IN] - flags controlling jit behavior
//
// Notes:
//  This is the most interesting 'toplevel' function in the JIT.  It goes through the operations of
//  importing, morphing, optimizations and code generation.  This is called from the EE through the
//  code:CILJit::compileMethod function.
//
//  For an overview of the structure of the JIT, see:
//   https://github.com/dotnet/runtime/blob/main/docs/design/coreclr/jit/ryujit-overview.md
//
//  Also called for inlinees, though they will only be run through the first few phases.
//
void Compiler::compCompile(void** methodCodePtr, uint32_t* methodCodeSize, JitFlags* compileFlags)
{
    compFunctionTraceStart();

    // Enable flow graph checks
    activePhaseChecks |= PhaseChecks::CHECK_FG;

    // Prepare for importation
    //
    auto preImportPhase = [this]() {
        if (compIsForInlining())
        {
            // Notify root instance that an inline attempt is about to import IL
            impInlineRoot()->m_inlineStrategy->NoteImport();
        }

        hashBv::Init(this);

        VarSetOps::AssignAllowUninitRhs(this, compCurLife, VarSetOps::UninitVal());

        // The temp holding the secret stub argument is used by fgImport() when importing the intrinsic.
        if (info.compPublishStubParam)
        {
            assert(lvaStubArgumentVar == BAD_VAR_NUM);
            lvaStubArgumentVar                     = lvaGrabTempWithImplicitUse(false DEBUGARG("stub argument"));
            lvaGetDesc(lvaStubArgumentVar)->lvType = TYP_I_IMPL;
        }
    };
    DoPhase(this, PHASE_PRE_IMPORT, preImportPhase);

    // If we're going to instrument code, we may need to prepare before
    // we import. Also do this before we read in any profile data.
    //
    if (compileFlags->IsSet(JitFlags::JIT_FLAG_BBINSTR))
    {
        DoPhase(this, PHASE_IBCPREP, &Compiler::fgPrepareToInstrumentMethod);
    }

    // Incorporate profile data.
    //
    // Note: the importer is sensitive to block weights, so this has
    // to happen before importation.
    //
    activePhaseChecks |= PhaseChecks::CHECK_PROFILE | PhaseChecks::CHECK_PROFILE_FLAGS;
    DoPhase(this, PHASE_INCPROFILE, &Compiler::fgIncorporateProfileData);

    activePhaseChecks |= PhaseChecks::CHECK_FG_INIT_BLOCK;
    DoPhase(this, PHASE_CANONICALIZE_ENTRY, &Compiler::fgCanonicalizeFirstBB);

    // If we are doing OSR, update flow to initially reach the appropriate IL offset.
    //
    if (opts.IsOSR())
    {
        fgFixEntryFlowForOSR();
    }

    // Enable the post-phase checks that use internal logic to decide when checking makes sense.
    //
    activePhaseChecks |=
        PhaseChecks::CHECK_EH | PhaseChecks::CHECK_LOOPS | PhaseChecks::CHECK_UNIQUE | PhaseChecks::CHECK_LINKED_LOCALS;

    // Import: convert the instrs in each basic block to a tree based intermediate representation
    //
    DoPhase(this, PHASE_IMPORTATION, &Compiler::fgImport);

    // If this is a failed inline attempt, we're done.
    //
    if (compIsForInlining() && compInlineResult->IsFailure())
    {
#ifdef FEATURE_JIT_METHOD_PERF
        if (pCompJitTimer != nullptr)
        {
#if MEASURE_CLRAPI_CALLS
            EndPhase(PHASE_CLR_API);
#endif
            pCompJitTimer->Terminate(this, CompTimeSummaryInfo::s_compTimeSummary, false);
        }
#endif

        return;
    }

    // If instrumenting, add block and class probes.
    //
    if (compileFlags->IsSet(JitFlags::JIT_FLAG_BBINSTR))
    {
        DoPhase(this, PHASE_IBCINSTR, &Compiler::fgInstrumentMethod);
    }

    // Expand any patchpoints
    //
    DoPhase(this, PHASE_PATCHPOINTS, &Compiler::fgTransformPatchpoints);

    // Transform indirect calls that require control flow expansion.
    //
    DoPhase(this, PHASE_INDXCALL, &Compiler::fgTransformIndirectCalls);

    // Cleanup un-imported BBs, cleanup un-imported or
    // partially imported try regions, add OSR step blocks.
    //
    DoPhase(this, PHASE_POST_IMPORT, &Compiler::fgPostImportationCleanup);

    // Capture and restore contexts around the body, if needed.
    //
    DoPhase(this, PHASE_ASYNC_SAVE_CONTEXTS, &Compiler::SaveAsyncContexts);

    // If we're importing for inlining, we're done.
    if (compIsForInlining())
    {

#ifdef FEATURE_JIT_METHOD_PERF
        if (pCompJitTimer != nullptr)
        {
#if MEASURE_CLRAPI_CALLS
            EndPhase(PHASE_CLR_API);
#endif
            pCompJitTimer->Terminate(this, CompTimeSummaryInfo::s_compTimeSummary, false);
        }
#endif

        return;
    }

    // At this point in the phase list, all the inlinee phases have
    // been run, and inlinee compiles have exited, so we should only
    // get this far if we are jitting the root method.
    noway_assert(!compIsForInlining());

    // Prepare for the morph phases
    //
    DoPhase(this, PHASE_MORPH_INIT, &Compiler::fgMorphInit);

    // Inline callee methods into this root method
    //
    DoPhase(this, PHASE_MORPH_INLINE, &Compiler::fgInline);

    // Record "start" values for post-inlining cycles and elapsed time.
    RecordStateAtEndOfInlining();

    if (opts.OptimizationEnabled())
    {
        // Try and resolve GDV checks if improved types were found during inlining
        //
        DoPhase(this, PHASE_RESOLVE_GDVS, &Compiler::fgResolveGDVs);

        // Build post-order and remove dead blocks
        //
        DoPhase(this, PHASE_DFS_BLOCKS1, &Compiler::fgDfsBlocksAndRemove);
    }

    // Transform each GT_ALLOCOBJ node into either an allocation helper call or
    // local variable allocation on the stack.
    ObjectAllocator objectAllocator(this); // PHASE_ALLOCATE_OBJECTS

    if (compObjectStackAllocation() && opts.OptimizationEnabled())
    {
        objectAllocator.EnableObjectStackAllocation();
    }

    objectAllocator.Run();

    // Add any internal blocks/trees we may need
    //
    DoPhase(this, PHASE_MORPH_ADD_INTERNAL, &Compiler::fgAddInternal);

#ifdef SWIFT_SUPPORT
    // Transform GT_RETURN nodes into GT_SWIFT_ERROR_RET nodes if this method has Swift error handling
    //
    DoPhase(this, PHASE_SWIFT_ERROR_RET, &Compiler::fgAddSwiftErrorReturns);
#endif // SWIFT_SUPPORT

    // Remove empty try regions (try/finally)
    //
    DoPhase(this, PHASE_EMPTY_TRY, &Compiler::fgRemoveEmptyTry);

    // Remove empty try regions (try/catch/fault)
    //
    DoPhase(this, PHASE_EMPTY_TRY_CATCH_FAULT, &Compiler::fgRemoveEmptyTryCatchOrTryFault);

    // Remove empty finally regions
    //
    DoPhase(this, PHASE_EMPTY_FINALLY, &Compiler::fgRemoveEmptyFinally);

    // Streamline chains of finally invocations
    //
    DoPhase(this, PHASE_MERGE_FINALLY_CHAINS, &Compiler::fgMergeFinallyChains);

    // Clone code in finallys to reduce overhead for non-exceptional paths
    //
    DoPhase(this, PHASE_CLONE_FINALLY, &Compiler::fgCloneFinally);

    // Do some flow-related optimizations
    //
    if (opts.OptimizationEnabled())
    {
        // Tail merge
        //
        DoPhase(this, PHASE_HEAD_TAIL_MERGE, [this]() {
            return fgHeadTailMerge(true);
        });

        // Merge common throw blocks
        //
        DoPhase(this, PHASE_MERGE_THROWS, &Compiler::fgTailMergeThrows);

        // Run an early flow graph simplification pass
        //
        DoPhase(this, PHASE_EARLY_UPDATE_FLOW_GRAPH, &Compiler::fgUpdateFlowGraphPhase);
    }

    // Promote struct locals
    //
    DoPhase(this, PHASE_PROMOTE_STRUCTS, &Compiler::fgPromoteStructs);

    // Enable early ref counting of locals
    //
    lvaRefCountState = RCS_EARLY;

    if (opts.OptimizationEnabled())
    {
        // Build post-order and remove dead blocks
        //
        DoPhase(this, PHASE_DFS_BLOCKS2, &Compiler::fgDfsBlocksAndRemove);

        fgNodeThreading = NodeThreading::AllLocals;
    }

    // Simplify local accesses and analyze address exposure.
    //
    DoPhase(this, PHASE_LOCAL_MORPH, &Compiler::fgLocalMorph);

    // Optimize away conversions to/from masks in local variables.
    //
    DoPhase(this, PHASE_OPTIMIZE_MASK_CONVERSIONS, &Compiler::fgOptimizeMaskConversions);

    // Do an early pass of liveness for forward sub and morph. This data is
    // valid until after morph.
    //
    DoPhase(this, PHASE_EARLY_LIVENESS, &Compiler::fgEarlyLiveness);

    // Run a simple forward substitution pass.
    //
    DoPhase(this, PHASE_FWD_SUB, &Compiler::fgForwardSub);

    // Promote struct locals based on primitive access patterns
    //
    DoPhase(this, PHASE_PHYSICAL_PROMOTION, &Compiler::PhysicalPromotion);

    // Expose candidates for implicit byref last-use copy elision.
    DoPhase(this, PHASE_IMPBYREF_COPY_OMISSION, &Compiler::fgMarkImplicitByRefCopyOmissionCandidates);

    // Locals tree list is no longer kept valid.
    fgNodeThreading = NodeThreading::None;

    // Apply the type update to implicit byref parameters; also choose (based on address-exposed
    // analysis) which implicit byref promotions to keep (requires copy to initialize) or discard.
    //
    DoPhase(this, PHASE_MORPH_IMPBYREF, &Compiler::fgRetypeImplicitByRefArgs);

#ifdef DEBUG
    // Now that locals have address-taken and implicit byref marked, we can safely apply stress.
    lvaStressLclFld();
    fgStress64RsltMul();
#endif // DEBUG

    // Morph the trees in all the blocks of the method
    //
    unsigned const preMorphBBCount = fgBBcount;
    DoPhase(this, PHASE_MORPH_GLOBAL, &Compiler::fgMorphBlocks);

    auto postMorphPhase = [this]() {
        // Fix any LclVar annotations on discarded struct promotion temps for implicit by-ref args
        fgMarkDemotedImplicitByRefArgs();
        lvaRefCountState       = RCS_INVALID;
        fgLocalVarLivenessDone = false;

        // Decide the kind of code we want to generate
        fgSetOptions();

        fgExpandQmarkNodes();

#ifdef DEBUG
        compCurBB = nullptr;
#endif // DEBUG

        // Enable IR checks
        activePhaseChecks |= PhaseChecks::CHECK_IR;
    };
    DoPhase(this, PHASE_POST_MORPH, postMorphPhase);

    // GS security checks for unsafe buffers
    //
    DoPhase(this, PHASE_GS_COOKIE, &Compiler::gsPhase);

    if (opts.OptimizationEnabled())
    {
        // Compute the block weights
        //
        DoPhase(this, PHASE_COMPUTE_BLOCK_WEIGHTS, &Compiler::fgComputeBlockWeights);

        // Try again to remove empty try finally/fault clauses
        //
        DoPhase(this, PHASE_EMPTY_FINALLY_2, &Compiler::fgRemoveEmptyFinally);

        // Remove empty try regions (try/finally)
        //
        DoPhase(this, PHASE_EMPTY_TRY_2, &Compiler::fgRemoveEmptyTry);

        // Remove empty try regions (try/catch/fault)
        //
        DoPhase(this, PHASE_EMPTY_TRY_CATCH_FAULT_2, &Compiler::fgRemoveEmptyTryCatchOrTryFault);

        // Run some flow graph optimizations (but don't reorder)
        //
        DoPhase(this, PHASE_OPTIMIZE_FLOW, &Compiler::optOptimizeFlow);

        // Second pass of tail merge
        //
        DoPhase(this, PHASE_HEAD_TAIL_MERGE2, [this]() {
            return fgHeadTailMerge(false);
        });

        // Compute DFS tree and remove all unreachable blocks.
        //
        DoPhase(this, PHASE_DFS_BLOCKS3, &Compiler::fgDfsBlocksAndRemove);

        auto adjustThrowEdgeLikelihoods = [this]() -> PhaseStatus {
            return ProfileSynthesis::AdjustThrowEdgeLikelihoods(this);
        };

        // Adjust heuristic-derived edge likelihoods into paths that are known to throw.
        //
        DoPhase(this, PHASE_ADJUST_THROW_LIKELIHOODS, adjustThrowEdgeLikelihoods);

        // Discover and classify natural loops (e.g. mark iterative loops as such).
        //
        DoPhase(this, PHASE_FIND_LOOPS, &Compiler::optFindLoopsPhase);

        // Re-establish profile consistency, now that inlining and morph have run.
        //
        DoPhase(this, PHASE_REPAIR_PROFILE_POST_MORPH, &Compiler::fgRepairProfile);

        // Invert loops
        //
        DoPhase(this, PHASE_INVERT_LOOPS, &Compiler::optInvertLoops);

        // Scale block weights and mark run rarely blocks.
        //
        DoPhase(this, PHASE_SET_BLOCK_WEIGHTS, &Compiler::optSetBlockWeights);

        // Clone loops with optimization opportunities, and choose one based on dynamic condition evaluation.
        //
        DoPhase(this, PHASE_CLONE_LOOPS, &Compiler::optCloneLoops);

        // Unroll loops
        //
        DoPhase(this, PHASE_UNROLL_LOOPS, &Compiler::optUnrollLoops);

        // Compute dominators and exceptional entry blocks
        //
        DoPhase(this, PHASE_COMPUTE_DOMINATORS, &Compiler::fgComputeDominators);
    }

#ifdef DEBUG
    fgDebugCheckLinks();
#endif

    // Morph multi-dimensional array operations.
    // (Consider deferring all array operation morphing, including single-dimensional array ops,
    // from global morph to here, so cloning doesn't have to deal with morphed forms.)
    //
    DoPhase(this, PHASE_MORPH_MDARR, &Compiler::fgMorphArrayOps);

    // Create the variable table (and compute variable ref counts)
    //
    DoPhase(this, PHASE_MARK_LOCAL_VARS, &Compiler::lvaMarkLocalVars);

    // IMPORTANT, after this point, locals are ref counted.
    // However, ref counts are not kept incrementally up to date.
    assert(lvaLocalVarRefCounted());

    // Figure out the order in which operators are to be evaluated
    //
    DoPhase(this, PHASE_FIND_OPER_ORDER, &Compiler::fgFindOperOrder);

    // Weave the tree lists. Anyone who modifies the tree shapes after
    // this point is responsible for calling fgSetStmtSeq() to keep the
    // nodes properly linked.
    //
    DoPhase(this, PHASE_SET_BLOCK_ORDER, &Compiler::fgSetBlockOrder);

    fgNodeThreading = NodeThreading::AllTrees;

    // At this point we know if we are fully interruptible or not
    if (opts.OptimizationEnabled())
    {
        bool doSsa                     = true;
        bool doEarlyProp               = true;
        bool doValueNum                = true;
        bool doLoopHoisting            = true;
        bool doCopyProp                = true;
        bool doOptimizeIVs             = true;
        bool doBranchOpt               = true;
        bool doCse                     = true;
        bool doAssertionProp           = true;
        bool doVNBasedIntrinExpansion  = true;
        bool doRangeAnalysis           = true;
        bool doRangeCheckCloning       = true;
        bool doVNBasedDeadStoreRemoval = true;

#if defined(OPT_CONFIG)
        doSsa                     = (JitConfig.JitDoSsa() != 0);
        doEarlyProp               = doSsa && (JitConfig.JitDoEarlyProp() != 0);
        doValueNum                = doSsa && (JitConfig.JitDoValueNumber() != 0);
        doLoopHoisting            = doValueNum && (JitConfig.JitDoLoopHoisting() != 0);
        doCopyProp                = doValueNum && (JitConfig.JitDoCopyProp() != 0);
        doBranchOpt               = doValueNum && (JitConfig.JitDoRedundantBranchOpts() != 0);
        doCse                     = doValueNum;
        doAssertionProp           = doValueNum && (JitConfig.JitDoAssertionProp() != 0);
        doRangeAnalysis           = doAssertionProp && (JitConfig.JitDoRangeAnalysis() != 0);
        doRangeCheckCloning       = doValueNum && doRangeAnalysis;
        doOptimizeIVs             = doAssertionProp && (JitConfig.JitDoOptimizeIVs() != 0);
        doVNBasedDeadStoreRemoval = doValueNum && (JitConfig.JitDoVNBasedDeadStoreRemoval() != 0);
        doVNBasedIntrinExpansion  = doValueNum;
#endif // defined(OPT_CONFIG)

        if (opts.optRepeat)
        {
            opts.optRepeatActive = true;
        }

        while (++opts.optRepeatIteration <= opts.optRepeatCount)
        {
#ifdef DEBUG
            if (verbose && opts.optRepeat)
            {
                printf("\n*************** JitOptRepeat: iteration %d of %d\n\n", opts.optRepeatIteration,
                       opts.optRepeatCount);
            }
#endif // DEBUG

            fgModified = false;

            if (doSsa)
            {
                // Build up SSA form for the IR
                //
                DoPhase(this, PHASE_BUILD_SSA, &Compiler::fgSsaBuild);
            }
            else
            {
                // At least do local var liveness; lowering depends on this.
                fgLocalVarLiveness();
            }

            if (doEarlyProp)
            {
                // Propagate array length and rewrite getType() method call
                //
                DoPhase(this, PHASE_EARLY_PROP, &Compiler::optEarlyProp);
            }

            if (doValueNum)
            {
                // Value number the trees
                //
                DoPhase(this, PHASE_VALUE_NUMBER, &Compiler::fgValueNumber);
            }

            if (doLoopHoisting)
            {
                // Hoist invariant code out of loops
                //
                DoPhase(this, PHASE_HOIST_LOOP_CODE, &Compiler::optHoistLoopCode);
            }

            if (doCopyProp)
            {
                // Perform VN based copy propagation
                //
                DoPhase(this, PHASE_VN_COPY_PROP, &Compiler::optVnCopyProp);
            }

            if (doBranchOpt)
            {
                // Optimize redundant branches
                //
                DoPhase(this, PHASE_OPTIMIZE_BRANCHES, &Compiler::optRedundantBranches);
            }
            else
            {
                // DFS tree is always invalid after this point.
                //
                fgInvalidateDfsTree();
            }

            if (doCse)
            {
                // Remove common sub-expressions
                //
                DoPhase(this, PHASE_OPTIMIZE_VALNUM_CSES, &Compiler::optOptimizeCSEs);
            }

            if (doAssertionProp)
            {
                // Assertion propagation
                //
                DoPhase(this, PHASE_ASSERTION_PROP_MAIN, &Compiler::optAssertionPropMain);
            }

            if (doRangeAnalysis)
            {
                // Bounds check elimination via range analysis
                //
                DoPhase(this, PHASE_OPTIMIZE_INDEX_CHECKS, &Compiler::rangeCheckPhase);
            }

            if (doOptimizeIVs)
            {
                // Simplify and optimize induction variables used in natural loops
                //
                DoPhase(this, PHASE_OPTIMIZE_INDUCTION_VARIABLES, &Compiler::optInductionVariables);
            }

            fgInvalidateDfsTree();

            if (doVNBasedDeadStoreRemoval)
            {
                // Note: this invalidates SSA and value numbers on tree nodes.
                //
                DoPhase(this, PHASE_VN_BASED_DEAD_STORE_REMOVAL, &Compiler::optVNBasedDeadStoreRemoval);
            }

            if (doRangeCheckCloning)
            {
                // Clone blocks with subsequent bounds checks
                //
                DoPhase(this, PHASE_RANGE_CHECK_CLONING, &Compiler::optRangeCheckCloning);
            }

            if (doVNBasedIntrinExpansion)
            {
                // Expand some intrinsics based on VN data
                //
                DoPhase(this, PHASE_VN_BASED_INTRINSIC_EXPAND, &Compiler::fgVNBasedIntrinsicExpansion);
            }

            // Conservatively mark all VNs as stale
            vnStore = nullptr;

            if (fgModified)
            {
                // update the flowgraph if we modified it during the optimization phase
                //
                DoPhase(this, PHASE_OPT_UPDATE_FLOW_GRAPH, &Compiler::fgUpdateFlowGraphPhase);
            }

            // Iterate if requested, resetting annotations first.
            if (opts.optRepeatIteration == opts.optRepeatCount)
            {
                // If we're done optimizing, just remove the PHIs
                //
                fgResetForSsa(/* deepClean */ false);
                break;
            }

            assert(opts.optRepeat);

            ResetOptAnnotations();
            RecomputeFlowGraphAnnotations();

#ifdef DEBUG
            if (verbose)
            {
                printf("Trees before next JitOptRepeat iteration:\n");
                fgDispBasicBlocks(true);
            }
#endif // DEBUG
        }

        if (opts.optRepeat)
        {
            opts.optRepeatActive = false;
        }
    }

    optLoopsCanonical = false;

#ifdef DEBUG
    DoPhase(this, PHASE_STRESS_SPLIT_TREE, &Compiler::StressSplitTree);
#endif

    // Try again to remove empty try finally/fault clauses
    //
    DoPhase(this, PHASE_EMPTY_FINALLY_3, &Compiler::fgRemoveEmptyFinally);

    // Remove empty try regions (try/finally)
    //
    DoPhase(this, PHASE_EMPTY_TRY_3, &Compiler::fgRemoveEmptyTry);

    // Remove empty try regions (try/catch/fault)
    //
    DoPhase(this, PHASE_EMPTY_TRY_CATCH_FAULT_3, &Compiler::fgRemoveEmptyTryCatchOrTryFault);

    if (UsesFunclets())
    {
        // Create funclets from the EH handlers.
        //
        DoPhase(this, PHASE_CREATE_FUNCLETS, &Compiler::fgCreateFunclets);
    }

    // Expand casts
    DoPhase(this, PHASE_EXPAND_CASTS, &Compiler::fgLateCastExpansion);

    // Expand runtime lookups (an optimization but we'd better run it in tier0 too)
    DoPhase(this, PHASE_EXPAND_RTLOOKUPS, &Compiler::fgExpandRuntimeLookups);

    // Partially inline static initializations
    DoPhase(this, PHASE_EXPAND_STATIC_INIT, &Compiler::fgExpandStaticInit);

    // Expand thread local access
    DoPhase(this, PHASE_EXPAND_TLS, &Compiler::fgExpandThreadLocalAccess);

    // Expand stack allocated arrays
    DoPhase(this, PHASE_EXPAND_STACK_ARR, &Compiler::fgExpandStackArrayAllocations);

    // Insert GC Polls
    DoPhase(this, PHASE_INSERT_GC_POLLS, &Compiler::fgInsertGCPolls);

    // Create any throw helper blocks that might be needed
    //
    DoPhase(this, PHASE_CREATE_THROW_HELPERS, &Compiler::fgCreateThrowHelperBlocks);

    if (opts.OptimizationEnabled())
    {
        // Conditional to switch conversion, and switch peeling
        //
        DoPhase(this, PHASE_SWITCH_RECOGNITION, &Compiler::optRecognizeAndOptimizeSwitchJumps);

        // Optimize boolean conditions
        //
        DoPhase(this, PHASE_OPTIMIZE_BOOLS, &Compiler::optOptimizeBools);

        // If conversion
        //
        DoPhase(this, PHASE_IF_CONVERSION, &Compiler::optIfConversion);

        // Run flow optimizations before reordering blocks
        //
        DoPhase(this, PHASE_OPTIMIZE_PRE_LAYOUT, &Compiler::optOptimizePreLayout);

        // Ensure profile is consistent before starting backend phases
        //
        DoPhase(this, PHASE_REPAIR_PROFILE_PRE_LAYOUT, &Compiler::fgRepairProfile);
    }

#ifdef DEBUG
    // Stash the current estimate of the function's size if necessary.
    if (verbose && opts.OptimizationEnabled())
    {
        compSizeEstimate  = 0;
        compCycleEstimate = 0;
        for (BasicBlock* const block : Blocks())
        {
            for (Statement* const stmt : block->Statements())
            {
                compSizeEstimate += stmt->GetCostSz();
                compCycleEstimate += stmt->GetCostEx();
            }
        }
    }
#endif

    // rationalize trees
    Rationalizer rat(this); // PHASE_RATIONALIZE
    rat.Run();

    fgNodeThreading = NodeThreading::LIR;

    // Enable this to gather statistical data such as
    // call and register argument info, flowgraph and loop info, etc.
    compJitStats();

#ifdef TARGET_ARM
    if (compLocallocUsed)
    {
        // We reserve REG_SAVED_LOCALLOC_SP to store SP on entry for stack unwinding
        codeGen->regSet.rsMaskResvd |= RBM_SAVED_LOCALLOC_SP;
    }
#endif // TARGET_ARM

    if (compIsAsync())
    {
        DoPhase(this, PHASE_ASYNC, &Compiler::TransformAsync);
    }

#ifdef TARGET_WASM
    // Transform any strongly connected components into reducible flow.
    //
    DoPhase(this, PHASE_DFS_BLOCKS_WASM, &Compiler::fgDfsBlocksAndRemove);
    DoPhase(this, PHASE_WASM_TRANSFORM_SCCS, &Compiler::fgWasmTransformSccs);
#endif

    // Assign registers to variables, etc.

    // Create the RA before Lowering, so that Lowering can call RA methods for
    // determining whether locals are register candidates and (for xarch) whether
    // a node is a containable memory op.
    m_regAlloc = GetRegisterAllocator(this);

    // Lower
    //
    m_pLowering = new (this, CMK_LSRA) Lowering(this, m_regAlloc); // PHASE_LOWERING
    m_pLowering->Run();

    // Set stack levels and analyze throw helper usage.
    StackLevelSetter stackLevelSetter(this);
    stackLevelSetter.Run();
    m_pLowering->FinalizeOutgoingArgSpace();

    FinalizeEH();

    // We can not add any new tracked variables after this point.
    lvaTrackedFixed = true;

    // Now that lowering is completed we can proceed to perform register allocation
    //
    auto regAllocPhase = [this] {
        m_regAlloc->doRegisterAllocation();
    };
    DoPhase(this, PHASE_LINEAR_SCAN, regAllocPhase);

    // Copied from rpPredictRegUse()
    SetFullPtrRegMapRequired(codeGen->GetInterruptible() || !codeGen->isFramePointerUsed());

#ifdef TARGET_WASM
    // Reorder blocks for wasm and figure out wasm control flow nesting
    //
    DoPhase(this, PHASE_WASM_CONTROL_FLOW, &Compiler::fgWasmControlFlow);
#else
    if (opts.OptimizationEnabled())
    {
        // We won't introduce new blocks from here on out,
        // so run the new block layout.
        //
        DoPhase(this, PHASE_OPTIMIZE_LAYOUT, &Compiler::fgSearchImprovedLayout);

        // Now that the flowgraph is finalized, run post-layout optimizations.
        //
        DoPhase(this, PHASE_OPTIMIZE_POST_LAYOUT, &Compiler::optOptimizePostLayout);

        // Determine start of cold region if we are hot/cold splitting
        //
        DoPhase(this, PHASE_DETERMINE_FIRST_COLD_BLOCK, &Compiler::fgDetermineFirstColdBlock);
    }
#endif // TARGET_WASM

#if FEATURE_LOOP_ALIGN
    // Place loop alignment instructions
    DoPhase(this, PHASE_ALIGN_LOOPS, &Compiler::placeLoopAlignInstructions);
#endif

    // The common phase checks and dumps are no longer relevant past this point.
    //
    activePhaseChecks = PhaseChecks::CHECK_NONE;
    activePhaseDumps  = PhaseDumps::DUMP_NONE;

    // Generate code
    codeGen->genGenerateCode(methodCodePtr, methodCodeSize);

#if TRACK_LSRA_STATS
    if (JitConfig.DisplayLsraStats() == 2)
    {
        m_regAlloc->dumpLsraStatsCsv(jitstdout());
    }
#endif // TRACK_LSRA_STATS

    // We're done -- set the active phase to the last phase
    // (which isn't really a phase)
    mostRecentlyActivePhase = PHASE_POST_EMIT;

#ifdef FEATURE_JIT_METHOD_PERF
    if (pCompJitTimer)
    {
#if MEASURE_CLRAPI_CALLS
        EndPhase(PHASE_CLR_API);
#else
        EndPhase(PHASE_POST_EMIT);
#endif
        pCompJitTimer->Terminate(this, CompTimeSummaryInfo::s_compTimeSummary, true);
    }
#endif

    // Generate PatchpointInfo
    generatePatchpointInfo();

    RecordStateAtEndOfCompilation();

    unsigned methodsCompiled = (unsigned)InterlockedIncrement((LONG*)&Compiler::jitTotalMethodCompiled);

    if (JitConfig.JitDisasmSummary() && !compIsForInlining())
    {
        char osrBuffer[20] = {0};
        if (opts.IsOSR())
        {
            // Tiering name already includes "OSR", we just want the IL offset
            sprintf_s(osrBuffer, 20, " @0x%x", info.compILEntry);
        }

#ifdef DEBUG
        const char* fullName = info.compFullName;
#else
        const char* fullName =
            eeGetMethodFullName(info.compMethodHnd, /* includeReturnType */ false, /* includeThisSpecifier */ false);
#endif

        char debugPart[128] = {0};
        INDEBUG(sprintf_s(debugPart, 128, ", hash=0x%08x%s", info.compMethodHash(), compGetStressMessage()));

        char metricPart[128] = {0};
#ifdef DEBUG
        if (JitConfig.JitMetrics() > 0)
        {
            sprintf_s(metricPart, 128, ", perfScore=%.2f, numCse=%u", Metrics.PerfScore, optCSEcount);
        }
#endif

        const bool hasProf = fgHaveProfileData();
        printf("%4d: JIT compiled %s [%s%s%s%s, IL size=%u, code size=%u%s%s]\n", methodsCompiled, fullName,
               compGetTieringName(), osrBuffer, hasProf ? " with " : "", hasProf ? compGetPgoSourceName() : "",
               info.compILCodeSize, *methodCodeSize, debugPart, metricPart);
        fflush(jitstdout());
    }

    compFunctionTraceEnd(*methodCodePtr, *methodCodeSize, false);
    JITDUMP("Method code size: %d\n", (unsigned)(*methodCodeSize));

#if FUNC_INFO_LOGGING
    if (compJitFuncInfoFile != nullptr)
    {
        assert(!compIsForInlining());
#ifdef DEBUG // We only have access to info.compFullName in DEBUG builds.
        fprintf(compJitFuncInfoFile, "%s\n", info.compFullName);
#elif FEATURE_SIMD
        fprintf(compJitFuncInfoFile, " %s\n", eeGetMethodFullName(info.compMethodHnd));
#endif
        fflush(compJitFuncInfoFile);
    }
#endif // FUNC_INFO_LOGGING
}

//----------------------------------------------------------------------------------------------
// FinalizeEH: Finalize EH information
//
void Compiler::FinalizeEH()
{
#if defined(FEATURE_EH_WINDOWS_X86)

    // Grab space for exception handling info on the frame
    //
    if (!UsesFunclets() && ehNeedsShadowSPslots())
    {
        // Recompute the handler nesting levels, as they may have changed.
        //
        unsigned const oldHandlerNestingCount = ehMaxHndNestingCount;
        ehMaxHndNestingCount                  = 0;

        if (compHndBBtabCount > 0)
        {
            for (int XTnum = compHndBBtabCount - 1; XTnum >= 0; XTnum--)
            {
                EHblkDsc* const HBtab             = &compHndBBtab[XTnum];
                unsigned const  enclosingHndIndex = HBtab->ebdEnclosingHndIndex;

                if (enclosingHndIndex != EHblkDsc::NO_ENCLOSING_INDEX)
                {
                    EHblkDsc* const enclosingHBtab  = &compHndBBtab[enclosingHndIndex];
                    unsigned const  newNestingLevel = enclosingHBtab->ebdHandlerNestingLevel + 1;
                    HBtab->ebdHandlerNestingLevel   = (unsigned short)newNestingLevel;

                    if (newNestingLevel > ehMaxHndNestingCount)
                    {
                        ehMaxHndNestingCount = newNestingLevel;
                    }
                }
                else
                {
                    HBtab->ebdHandlerNestingLevel = 0;
                }
            }

            // When there is EH, we need to record nesting level + 1
            //
            ehMaxHndNestingCount++;
        }

        if (oldHandlerNestingCount != ehMaxHndNestingCount)
        {
            JITDUMP("Finalize EH: max handler nesting count now %u (was %u)\n", oldHandlerNestingCount,
                    ehMaxHndNestingCount);
        }

        // The first slot is reserved for ICodeManager::FixContext(ppEndRegion)
        // ie. the offset of the end-of-last-executed-filter
        unsigned slotsNeeded = 1;

        unsigned handlerNestingLevel = ehMaxHndNestingCount;

        if (opts.compDbgEnC && (handlerNestingLevel < (unsigned)MAX_EnC_HANDLER_NESTING_LEVEL))
            handlerNestingLevel = (unsigned)MAX_EnC_HANDLER_NESTING_LEVEL;

        slotsNeeded += handlerNestingLevel;

        // For a filter (which can be active at the same time as a catch/finally handler)
        slotsNeeded++;
        // For zero-termination of the shadow-Stack-pointer chain
        slotsNeeded++;

        lvaShadowSPslotsVar = lvaGrabTempWithImplicitUse(false DEBUGARG("lvaShadowSPslotsVar"));
        lvaSetStruct(lvaShadowSPslotsVar, typGetBlkLayout(slotsNeeded * TARGET_POINTER_SIZE), false);
        lvaSetVarAddrExposed(lvaShadowSPslotsVar DEBUGARG(AddressExposedReason::EXTERNALLY_VISIBLE_IMPLICITLY));
    }

    // Build up a mapping from EH IDs to EHblkDsc*
    //
    assert(m_EHIDtoEHblkDsc == nullptr);

    if (compHndBBtabCount > 0)
    {
        m_EHIDtoEHblkDsc = new (getAllocator()) EHIDtoEHblkDscMap(getAllocator());

        for (unsigned XTnum = 0; XTnum < compHndBBtabCount; XTnum++)
        {
            EHblkDsc* const HBtab = &compHndBBtab[XTnum];
            m_EHIDtoEHblkDsc->Set(HBtab->ebdID, HBtab);
        }
    }

#endif // FEATURE_EH_WINDOWS_X86

    // We should not make any more alterations to the EH table structure.
    //
    ehTableFinalized = true;
}

#if FEATURE_LOOP_ALIGN

//------------------------------------------------------------------------
// shouldAlignLoop: Check if it is legal and profitable to align a loop.
//
// Parameters:
//   loop - The loop
//   top  - First block of the loop that appears in lexical order
//
// Returns:
//    True if it is legal and profitable to align this loop.
//
// Remarks:
//   All innermost loops whose block weight meets a threshold are candidates for alignment.
//   The top block of the loop is marked with the BBF_LOOP_ALIGN flag to indicate this.
//
//   Depends on block weights being set.
//
bool Compiler::shouldAlignLoop(FlowGraphNaturalLoop* loop, BasicBlock* top)
{
    if (loop->GetChild() != nullptr)
    {
        JITDUMP("Skipping alignment for " FMT_LP "; not an innermost loop\n", loop->GetIndex());
        return false;
    }

    if (top == fgFirstBB)
    {
        // Adding align instruction in prolog is not supported.
        // TODO: Insert an empty block before the loop, if want to align it, so we have a place to put
        // the align instruction.
        JITDUMP("Skipping alignment for " FMT_LP "; loop starts in first block\n", loop->GetIndex());
        return false;
    }

    if (top->HasFlag(BBF_COLD))
    {
        JITDUMP("Skipping alignment for cold loop " FMT_LP "\n", loop->GetIndex());
        return false;
    }

    bool hasCall = loop->VisitLoopBlocks([](BasicBlock* block) {
        for (GenTree* tree : LIR::AsRange(block))
        {
            if (tree->IsCall())
            {
                return BasicBlockVisit::Abort;
            }
        }

        return BasicBlockVisit::Continue;
    }) == BasicBlockVisit::Abort;

    if (hasCall)
    {
        // Heuristic: it is not valuable to align loops with calls.
        JITDUMP("Skipping alignment for " FMT_LP "; loop contains call\n", loop->GetIndex());
        return false;
    }

    assert(!top->IsFirst());

    if (UsesCallFinallyThunks() && top->Prev()->KindIs(BBJ_CALLFINALLY))
    {
        // It must be a retless BBJ_CALLFINALLY if we get here.
        assert(!top->Prev()->isBBCallFinallyPair());

        // If the block before the loop start is a retless BBJ_CALLFINALLY
        // with UsesCallFinallyThunks, we can't add alignment
        // because it will affect reported EH region range. For x86 (where
        // !UsesCallFinallyThunks), we can allow this.

        JITDUMP("Skipping alignment for " FMT_LP "; its top block follows a CALLFINALLY block\n", loop->GetIndex());
        return false;
    }

    if (top->Prev()->isBBCallFinallyPairTail())
    {
        // If the previous block is the BBJ_CALLFINALLYRET of a
        // BBJ_CALLFINALLY/BBJ_CALLFINALLYRET pair, then we can't add alignment
        // because we can't add instructions in that block. In the
        // UsesCallFinallyThunks case, it would affect the reported EH, as above.
        JITDUMP("Skipping alignment for " FMT_LP "; its top block follows a CALLFINALLY/ALWAYS pair\n",
                loop->GetIndex());
        return false;
    }

    // Now we have an innerloop candidate that might need alignment

    weight_t topWeight     = top->getBBWeight(this);
    weight_t compareWeight = opts.compJitAlignLoopMinBlockWeight * BB_UNITY_WEIGHT;
    if (topWeight < compareWeight)
    {
        JITDUMP("Skipping alignment for " FMT_LP " that starts at " FMT_BB ", weight=" FMT_WT " < " FMT_WT ".\n",
                loop->GetIndex(), top->bbNum, topWeight, compareWeight);
        return false;
    }

    JITDUMP("Aligning " FMT_LP " that starts at " FMT_BB ", weight=" FMT_WT " >= " FMT_WT ".\n", loop->GetIndex(),
            top->bbNum, topWeight, compareWeight);
    return true;
}

//------------------------------------------------------------------------
// placeLoopAlignInstructions: determine where to place alignment padding
//
// Returns:
//    Suitable phase status
//
// Notes:
//    Iterate over all the blocks and determine
//    the best position to place the 'align' instruction. Inserting 'align'
//    instructions after an unconditional branch is preferred over inserting
//    in the block before the loop. In case there are multiple blocks
//    having 'jmp', the one that has lower weight is preferred.
//    If the block having 'jmp' is hotter than the block before the loop,
//    the align will still be placed after 'jmp' because the processor should
//    be smart enough to not fetch extra instruction beyond jmp.
//
PhaseStatus Compiler::placeLoopAlignInstructions()
{
    JITDUMP("*************** In placeLoopAlignInstructions()\n");

    if (!codeGen->ShouldAlignLoops())
    {
        JITDUMP("Not aligning loops; ShouldAlignLoops is false\n");
        return PhaseStatus::MODIFIED_NOTHING;
    }

    if (!fgMightHaveNaturalLoops)
    {
#ifdef DEBUG
        // If false, then we expect there to be no natural loops. Phases
        // between loop finding and loop alignment that may introduce loops
        // should conservatively set fgMightHaveNaturalLoops.
        FlowGraphDfsTree*      dfsTree = fgComputeDfs();
        FlowGraphNaturalLoops* loops   = FlowGraphNaturalLoops::Find(dfsTree);
        assert(loops->NumLoops() == 0);
#endif

        JITDUMP("Not checking for any loops as fgMightHaveNaturalLoops is false\n");
        return PhaseStatus::MODIFIED_NOTHING;
    }

    FlowGraphDfsTree*      dfsTree = fgComputeDfs();
    FlowGraphNaturalLoops* loops   = FlowGraphNaturalLoops::Find(dfsTree);

    // fgMightHaveNaturalLoops being true does not necessarily mean there's still any more loops left.
    if (loops->NumLoops() == 0)
    {
        JITDUMP("No natural loops found\n");
        return PhaseStatus::MODIFIED_NOTHING;
    }

    BlockToNaturalLoopMap* blockToLoop = BlockToNaturalLoopMap::Build(loops);

    BitVecTraits loopTraits((unsigned)loops->NumLoops(), this);
    BitVec       seenLoops(BitVecOps::MakeEmpty(&loopTraits));
    BitVec       alignedLoops(BitVecOps::MakeEmpty(&loopTraits));

    bool        madeChanges   = false;
    weight_t    minBlockSoFar = BB_MAX_WEIGHT;
    BasicBlock* bbHavingAlign = nullptr;

    for (BasicBlock* const block : Blocks())
    {
        FlowGraphNaturalLoop* loop = blockToLoop->GetLoop(block);

        // If this is the first block of a loop then see if we should align it
        if ((loop != nullptr) && BitVecOps::TryAddElemD(&loopTraits, seenLoops, loop->GetIndex()) &&
            shouldAlignLoop(loop, block))
        {
            block->SetFlags(BBF_LOOP_ALIGN);
            BitVecOps::AddElemD(&loopTraits, alignedLoops, loop->GetIndex());
            Metrics.LoopAlignmentCandidates++;

            BasicBlock* prev = block->Prev();
            // shouldAlignLoop should have guaranteed these properties.
            assert((prev != nullptr) && !prev->HasFlag(BBF_COLD));

            if (bbHavingAlign == nullptr)
            {
                // If jmp was not found, then block before the loop start is where align instruction will be added.

                bbHavingAlign = prev;
                JITDUMP("Marking " FMT_BB " before the loop with BBF_HAS_ALIGN for loop at " FMT_BB "\n", prev->bbNum,
                        block->bbNum);
            }
            else
            {
                JITDUMP("Marking " FMT_BB " that ends with unconditional jump with BBF_HAS_ALIGN for loop at " FMT_BB
                        "\n",
                        bbHavingAlign->bbNum, block->bbNum);
            }

            madeChanges = true;
            bbHavingAlign->SetFlags(BBF_HAS_ALIGN);

            minBlockSoFar = BB_MAX_WEIGHT;
            bbHavingAlign = nullptr;

            continue;
        }

        // Otherwise check if this is a candidate block for placing alignment for a future loop.
        // If there is an unconditional jump that won't be removed
        if (opts.compJitHideAlignBehindJmp && block->KindIs(BBJ_ALWAYS) && !block->CanRemoveJumpToNext(this))
        {
            // Track the lower weight blocks
            if (block->bbWeight < minBlockSoFar)
            {
                if ((loop == nullptr) || !BitVecOps::IsMember(&loopTraits, alignedLoops, loop->GetIndex()))
                {
                    // Ok to insert align instruction in this block because it is not part of any aligned loop.
                    minBlockSoFar = block->bbWeight;
                    bbHavingAlign = block;
                    JITDUMP(FMT_BB ", bbWeight=" FMT_WT " ends with unconditional 'jmp' \n", block->bbNum,
                            block->bbWeight);
                }
            }
        }
    }

    JITDUMP("Found %d candidates for loop alignment\n", Metrics.LoopAlignmentCandidates);

    return madeChanges ? PhaseStatus::MODIFIED_EVERYTHING : PhaseStatus::MODIFIED_NOTHING;
}

#endif

//------------------------------------------------------------------------
// StressSplitTree: A phase that stresses the gtSplitTree function.
//
// Returns:
//    Suitable phase status
//
// Notes:
//   Stress is applied on a function-by-function basis
//
PhaseStatus Compiler::StressSplitTree()
{
    if (compStressCompile(STRESS_SPLIT_TREES_RANDOMLY, 10))
    {
        SplitTreesRandomly();
        return PhaseStatus::MODIFIED_EVERYTHING;
    }

    if (compStressCompile(STRESS_SPLIT_TREES_REMOVE_COMMAS, 10))
    {
        SplitTreesRemoveCommas();
        return PhaseStatus::MODIFIED_EVERYTHING;
    }

    return PhaseStatus::MODIFIED_NOTHING;
}

//------------------------------------------------------------------------
// SplitTreesRandomly: Split all statements at a random location.
//
void Compiler::SplitTreesRandomly()
{
#ifdef DEBUG
    CLRRandom rng;
    rng.Init(info.compMethodHash() ^ 0x077cc4d4);

    // Splitting creates a lot of new locals. Set a limit on how many we end up creating here.
    unsigned  maxLvaCount = max(lvaCount * 2, 50000u);
    int       numSplit    = 0;
    const int splitLimit  = JitConfig.JitStressSplitTreeLimit();

    for (BasicBlock* block : Blocks())
    {
        for (Statement* stmt : block->NonPhiStatements())
        {
            if ((splitLimit >= 0) && (numSplit >= splitLimit))
            {
                JITDUMP("Reached split limit (%d) -- stopping\n", splitLimit);
                return;
            }

            int numTrees = 0;
            for (GenTree* tree : stmt->TreeList())
            {
                if (tree->OperIs(GT_JTRUE)) // Due to relop invariant
                {
                    continue;
                }

                numTrees++;
            }

            int splitTree = rng.Next(numTrees);
            for (GenTree* tree : stmt->TreeList())
            {
                if (tree->OperIs(GT_JTRUE))
                    continue;

                if (splitTree == 0)
                {
                    JITDUMP("Splitting " FMT_STMT " at [%06u]\n", stmt->GetID(), dspTreeID(tree));
                    Statement* newStmt;
                    GenTree**  use;
                    if (gtSplitTree(block, stmt, tree, &newStmt, &use))
                    {
                        while ((newStmt != nullptr) && (newStmt != stmt))
                        {
                            fgMorphStmtBlockOps(block, newStmt);
                            newStmt = newStmt->GetNextStmt();
                        }

                        fgMorphStmtBlockOps(block, stmt);
                        gtUpdateStmtSideEffects(stmt);
                        numSplit++;
                    }

                    break;
                }

                splitTree--;
            }

            if (lvaCount > maxLvaCount)
            {
                JITDUMP("Created too many locals (at %u) -- stopping\n", lvaCount);
                return;
            }
        }
    }

    JITDUMP("Split %d trees\n", numSplit);

#endif
}

//------------------------------------------------------------------------
// SplitTreesRemoveCommas: Split trees to remove all commas.
//
void Compiler::SplitTreesRemoveCommas()
{
    // Splitting creates a lot of new locals. Set a limit on how many we end up creating here.
    unsigned maxLvaCount = max(lvaCount * 2, 50000u);

    for (BasicBlock* block : Blocks())
    {
        Statement* stmt = block->FirstNonPhiDef();
        while (stmt != nullptr)
        {
            Statement* nextStmt = stmt->GetNextStmt();
            for (GenTree* tree : stmt->TreeList())
            {
                if (!tree->OperIs(GT_COMMA))
                {
                    continue;
                }

                // Supporting this weird construct would require additional
                // handling, we need to sort of move the comma into to the
                // next node in execution order. We don't see this so just
                // skip it.
                assert(!tree->IsReverseOp());

                JITDUMP("Removing COMMA [%06u]\n", dspTreeID(tree));
                Statement* newStmt;
                GenTree**  use;
                gtSplitTree(block, stmt, tree, &newStmt, &use);
                GenTree* op1SideEffects = nullptr;
                gtExtractSideEffList(tree->gtGetOp1(), &op1SideEffects);

                if (op1SideEffects != nullptr)
                {
                    Statement* op1Stmt = fgNewStmtFromTree(op1SideEffects);
                    fgInsertStmtBefore(block, stmt, op1Stmt);
                    if (newStmt == nullptr)
                    {
                        newStmt = op1Stmt;
                    }
                }

                *use = tree->gtGetOp2();

                for (Statement* cur = newStmt; (cur != nullptr) && (cur != stmt); cur = cur->GetNextStmt())
                {
                    fgMorphStmtBlockOps(block, cur);
                }

                fgMorphStmtBlockOps(block, stmt);
                gtUpdateStmtSideEffects(stmt);

                if (lvaCount > maxLvaCount)
                {
                    JITDUMP("Created too many locals (at %u) -- stopping\n", lvaCount);
                    return;
                }

                // Morphing block ops can introduce commas (and the original
                // statement can also have more commas left). Proceed from the
                // earliest newly introduced statement.
                nextStmt = newStmt != nullptr ? newStmt : stmt;
                break;
            }

            stmt = nextStmt;
        }
    }

    for (BasicBlock* block : Blocks())
    {
        for (Statement* stmt : block->NonPhiStatements())
        {
            for (GenTree* tree : stmt->TreeList())
            {
                assert(!tree->OperIs(GT_COMMA));
            }
        }
    }
}

//------------------------------------------------------------------------
// generatePatchpointInfo: allocate and fill in patchpoint info data,
//    and report it to the VM
//
void Compiler::generatePatchpointInfo()
{
    if (!doesMethodHavePatchpoints() && !doesMethodHavePartialCompilationPatchpoints())
    {
        // Nothing to report
        return;
    }

    // Patchpoints are only found in Tier0 code, which is unoptimized, and so
    // should always have frame pointer.
    assert(codeGen->isFramePointerUsed());

    // Allocate patchpoint info storage from runtime, and fill in initial bits of data.
    const unsigned        patchpointInfoSize = PatchpointInfo::ComputeSize(info.compLocalsCount);
    PatchpointInfo* const patchpointInfo     = (PatchpointInfo*)info.compCompHnd->allocateArray(patchpointInfoSize);

    // Patchpoint offsets always refer to "virtual frame offsets".
    //
    // For x64 this falls out because Tier0 frames are always FP frames, and so the FP-relative
    // offset is what we want.
    //
    // For arm64, if the frame pointer is not at the top of the frame, we need to adjust the
    // offset.

#if defined(TARGET_AMD64)
    // We add +TARGET_POINTER_SIZE here is to account for the slot that Jit_Patchpoint
    // creates when it simulates calling the OSR method (the "pseudo return address" slot).
    // This is effectively a new slot at the bottom of the Tier0 frame.
    //
    const int totalFrameSize = codeGen->genTotalFrameSize() + TARGET_POINTER_SIZE;
    const int offsetAdjust   = 0;
#elif defined(TARGET_ARM64) || defined(TARGET_LOONGARCH64) || defined(TARGET_RISCV64)
    // SP is not manipulated by calls so no frame size adjustment needed.
    // Local Offsets may need adjusting, if FP is at bottom of frame.
    //
    const int totalFrameSize = codeGen->genTotalFrameSize();
    const int offsetAdjust   = codeGen->genSPtoFPdelta() - totalFrameSize;
#else
    NYI("patchpoint info generation");
    const int offsetAdjust   = 0;
    const int totalFrameSize = 0;
#endif

    patchpointInfo->Initialize(info.compLocalsCount, totalFrameSize);

    JITDUMP("--OSR--- Total Frame Size %d, local offset adjust is %d\n", patchpointInfo->TotalFrameSize(),
            offsetAdjust);

    // We record offsets for all the "locals" here. Could restrict
    // this to just the IL locals with some extra logic, and save a bit of space,
    // but would need to adjust all consumers, too.
    for (unsigned lclNum = 0; lclNum < info.compLocalsCount; lclNum++)
    {
        // If there are shadowed params, the patchpoint info should refer to the shadow copy.
        //
        unsigned varNum = lclNum;

        if (gsShadowVarInfo != nullptr)
        {
            unsigned const shadowNum = gsShadowVarInfo[lclNum].shadowCopy;
            if (shadowNum != BAD_VAR_NUM)
            {
                assert(shadowNum < lvaCount);
                assert(shadowNum >= info.compLocalsCount);

                varNum = shadowNum;
            }
        }

        LclVarDsc* const varDsc = lvaGetDesc(varNum);

        // We expect all these to have stack homes, and be FP relative
        assert(varDsc->lvOnFrame);
        assert(varDsc->lvFramePointerBased);

        // Record FramePtr relative offset (no localloc yet)
        // Note if IL stream contained an address-of that potentially leads to exposure.
        // That bit of IL might be skipped by OSR partial importation.
        const bool isExposed = varDsc->lvHasLdAddrOp;
        patchpointInfo->SetOffsetAndExposure(lclNum, varDsc->GetStackOffset() + offsetAdjust, isExposed);

        JITDUMP("--OSR-- V%02u is at virtual offset %d%s%s\n", lclNum, patchpointInfo->Offset(lclNum),
                patchpointInfo->IsExposed(lclNum) ? " (exposed)" : "", (varNum != lclNum) ? " (shadowed)" : "");
    }

    // Special offsets
    //
    if (lvaReportParamTypeArg())
    {
        const int offset = lvaCachedGenericContextArgOffset();
        patchpointInfo->SetGenericContextArgOffset(offset + offsetAdjust);
        JITDUMP("--OSR-- cached generic context virtual offset is %d\n", patchpointInfo->GenericContextArgOffset());
    }

    if (lvaKeepAliveAndReportThis())
    {
        const int offset = lvaCachedGenericContextArgOffset();
        patchpointInfo->SetKeptAliveThisOffset(offset + offsetAdjust);
        JITDUMP("--OSR-- kept-alive this virtual offset is %d\n", patchpointInfo->KeptAliveThisOffset());
    }

    if (compGSReorderStackLayout)
    {
        assert(lvaGSSecurityCookie != BAD_VAR_NUM);
        LclVarDsc* const varDsc = lvaGetDesc(lvaGSSecurityCookie);
        patchpointInfo->SetSecurityCookieOffset(varDsc->GetStackOffset() + offsetAdjust);
        JITDUMP("--OSR-- security cookie V%02u virtual offset is %d\n", lvaGSSecurityCookie,
                patchpointInfo->SecurityCookieOffset());
    }

    if (lvaMonAcquired != BAD_VAR_NUM)
    {
        LclVarDsc* const varDsc = lvaGetDesc(lvaMonAcquired);
        patchpointInfo->SetMonitorAcquiredOffset(varDsc->GetStackOffset() + offsetAdjust);
        JITDUMP("--OSR-- monitor acquired V%02u virtual offset is %d\n", lvaMonAcquired,
                patchpointInfo->MonitorAcquiredOffset());
    }

#if defined(TARGET_AMD64)
    // Record callee save registers.
    // Currently only needed for x64.
    //
    regMaskTP rsPushRegs = codeGen->regSet.rsGetModifiedCalleeSavedRegsMask();
    rsPushRegs |= RBM_FPBASE;
    patchpointInfo->SetCalleeSaveRegisters((uint64_t)rsPushRegs);
    JITDUMP("--OSR-- Tier0 callee saves: ");
    JITDUMPEXEC(dspRegMask((regMaskTP)patchpointInfo->CalleeSaveRegisters()));
    JITDUMP("\n");
#endif

    // Register this with the runtime.
    info.compCompHnd->setPatchpointInfo(patchpointInfo);
}

//------------------------------------------------------------------------
// ResetOptAnnotations: Clear annotations produced during global optimizations.
//
// Notes:
//    The intent of this method is to clear any information typically assumed
//    to be set only once; it is used between iterations when JitOptRepeat is
//    in effect.
//
void Compiler::ResetOptAnnotations()
{
    assert(opts.optRepeat);
    assert(JitConfig.JitOptRepeatCount() > 0);
    fgResetForSsa(/* deepClean */ true);
    vnStore                    = nullptr;
    m_blockToEHPreds           = nullptr;
    m_dominancePreds           = nullptr;
    fgSsaPassesCompleted       = 0;
    fgVNPassesCompleted        = 0;
    fgSsaValid                 = false;
    m_nodeToLoopMemoryBlockMap = nullptr;

    for (BasicBlock* const block : Blocks())
    {
        for (Statement* const stmt : block->Statements())
        {
            for (GenTree* const tree : stmt->TreeList())
            {
                tree->ClearVN();
                tree->ClearAssertion();
                tree->gtCSEnum = NO_CSE;
            }
        }
    }
}

//------------------------------------------------------------------------
// RecomputeLoopInfo: Recompute flow graph annotations between opt-repeat iterations.
//
// Notes:
//    The intent of this method is to update all annotations computed on the flow graph
//    these annotations may have become stale during optimization, and need to be
//    up-to-date before running another iteration of optimizations.
//
void Compiler::RecomputeFlowGraphAnnotations()
{
    assert(opts.optRepeat);
    assert(JitConfig.JitOptRepeatCount() > 0);
    // Recompute reachability sets, dominators, and loops.
    optResetLoopInfo();

    fgDfsBlocksAndRemove();
    optFindLoops();

    // Should we call this using the phase method:
    //    DoPhase(this, PHASE_SET_BLOCK_WEIGHTS, &Compiler::optSetBlockWeights);
    // ? It could be called multiple times.
    optSetBlockWeights();

    if (m_domTree == nullptr)
    {
        m_domTree = FlowGraphDominatorTree::Build(m_dfsTree);
    }
}

/*****************************************************************************/
void Compiler::ProcessShutdownWork(ICorStaticInfo* statInfo)
{
}

/*****************************************************************************/

#ifdef DEBUG
void* forceFrameJIT; // used to force to frame &useful for fastchecked debugging

bool Compiler::skipMethod()
{
    static ConfigMethodRange fJitRange;
    fJitRange.EnsureInit(JitConfig.JitRange());
    assert(!fJitRange.Error());

    // Normally JitConfig.JitRange() is null, we don't want to skip
    // jitting any methods.
    //
    // So, the logic below relies on the fact that a null range string
    // passed to ConfigMethodRange represents the set of all methods.

    if (!fJitRange.Contains(info.compMethodHash()))
    {
        return true;
    }

    if (JitConfig.JitExclude().contains(info.compMethodHnd, info.compClassHnd, &info.compMethodInfo->args))
    {
        return true;
    }

    if (!JitConfig.JitInclude().isEmpty() &&
        !JitConfig.JitInclude().contains(info.compMethodHnd, info.compClassHnd, &info.compMethodInfo->args))
    {
        return true;
    }

    return false;
}

#endif

/*****************************************************************************/

int Compiler::compCompileAfterInit(CORINFO_MODULE_HANDLE classPtr,
                                   void**                methodCodePtr,
                                   uint32_t*             methodCodeSize,
                                   JitFlags*             compileFlags)
{
    // compInit should have set these already.
    noway_assert(info.compMethodInfo != nullptr);
    noway_assert(info.compCompHnd != nullptr);
    noway_assert(info.compMethodHnd != nullptr);

#ifdef FEATURE_JIT_METHOD_PERF
    static bool checkedForJitTimeLog = false;

    if (!checkedForJitTimeLog)
    {
        InterlockedCompareExchangeT(&Compiler::compJitTimeLogFilename, JitConfig.JitTimeLogFile(), NULL);

        // At a process or module boundary clear the file and start afresh.
        JitTimer::PrintCsvHeader();

        checkedForJitTimeLog = true;
    }
    if ((Compiler::compJitTimeLogFilename != nullptr) || (JitTimeLogCsv() != nullptr))
    {
        pCompJitTimer = JitTimer::Create(this, info.compMethodInfo->ILCodeSize);
    }
#endif // FEATURE_JIT_METHOD_PERF

#ifdef DEBUG
    Compiler* me  = this;
    forceFrameJIT = (void*)&me; // let us see the this pointer in fastchecked build
#endif

#if FUNC_INFO_LOGGING
    const char* tmpJitFuncInfoFilename = JitConfig.JitFuncInfoFile();

    if (tmpJitFuncInfoFilename != nullptr)
    {
        const char* oldFuncInfoFileName =
            InterlockedCompareExchangeT(&compJitFuncInfoFilename, tmpJitFuncInfoFilename, NULL);
        if (oldFuncInfoFileName == nullptr)
        {
            assert(compJitFuncInfoFile == nullptr);
            compJitFuncInfoFile = fopen_utf8(compJitFuncInfoFilename, "a");
            if (compJitFuncInfoFile == nullptr)
            {
#if defined(DEBUG) && !defined(HOST_UNIX) // no 'perror' in the PAL
                perror("Failed to open JitFuncInfoLogFile");
#endif // defined(DEBUG) && !defined(HOST_UNIX)
            }
        }
    }
#endif // FUNC_INFO_LOGGING

    // if (s_compMethodsCount==0) setvbuf(jitstdout(), NULL, _IONBF, 0);

    if (compIsForInlining())
    {
        compileFlags->Clear(JitFlags::JIT_FLAG_OSR);
        info.compILEntry        = 0;
        info.compPatchpointInfo = nullptr;
    }
    else if (compileFlags->IsSet(JitFlags::JIT_FLAG_OSR))
    {
        // Fetch OSR info from the runtime
        info.compPatchpointInfo = info.compCompHnd->getOSRInfo(&info.compILEntry);
        assert(info.compPatchpointInfo != nullptr);
    }

    // If we are not compiling for a matched VM, then we are getting JIT flags that don't match our target
    // architecture. The two main examples here are an ARM targeting altjit hosted on x86 and an ARM64
    // targeting altjit hosted on x64. (Though with cross-bitness work, the host doesn't necessarily need
    // to be of the same bitness.) In these cases, we need to fix up the JIT flags to be appropriate for
    // the target, as the VM's expected target may overlap bit flags with different meaning to our target.
    // Note that it might be better to do this immediately when setting the JIT flags in CILJit::compileMethod()
    // (when JitFlags::SetFromFlags() is called), but this is close enough. (To move this logic to
    // CILJit::compileMethod() would require moving the info.compMatchedVM computation there as well.)
    //
    // We additionally want to do this for AltJit so that we can validate ISAs that the underlying CPU may
    // not support directly. Doing this check later, after opts.altJit has been initialized might be better
    // but it requires moving the whole set of logic down into compCompileHelper after compInitOptions has
    // run and we're going to end up exiting early if JIT_FLAG_ALT_JIT and opts.altJit don't match anyways

    bool enableAvailableIsas = !info.compMatchedVM;

#ifdef DEBUG
    if (compileFlags->IsSet(JitFlags::JIT_FLAG_ALT_JIT) && JitConfig.RunAltJitCode() == 0)
    {
        enableAvailableIsas = true;
    }
#endif // DEBUG

    if (enableAvailableIsas)
    {
        CORINFO_InstructionSetFlags currentInstructionSetFlags = compileFlags->GetInstructionSetFlags();
        CORINFO_InstructionSetFlags instructionSetFlags;

        // We need to assume, by default, that all flags coming from the VM are invalid.
        instructionSetFlags.Reset();

        // We then add each available instruction set for the target architecture provided
        // that the corresponding JitConfig switch hasn't explicitly asked for it to be
        // disabled. This allows us to default to "everything" supported for altjit scenarios
        // while also still allowing instruction set opt-out providing users with the ability
        // to, for example, see and debug ARM64 codegen for any desired CPU configuration without
        // needing to have the hardware in question.

#if defined(TARGET_ARM64)
        if (info.compMatchedVM)
        {
            // Keep the existing VectorT* ISAs.
            if (currentInstructionSetFlags.HasInstructionSet(InstructionSet_VectorT128))
            {
                instructionSetFlags.AddInstructionSet(InstructionSet_VectorT128);
            }
        }

        instructionSetFlags.AddInstructionSet(InstructionSet_ArmBase);
        instructionSetFlags.AddInstructionSet(InstructionSet_AdvSimd);

        if (JitConfig.EnableArm64Aes() != 0)
        {
            instructionSetFlags.AddInstructionSet(InstructionSet_Aes);
        }

        if (JitConfig.EnableArm64Crc32() != 0)
        {
            instructionSetFlags.AddInstructionSet(InstructionSet_Crc32);
        }

        if (JitConfig.EnableArm64Dp() != 0)
        {
            instructionSetFlags.AddInstructionSet(InstructionSet_Dp);
        }

        if (JitConfig.EnableArm64Rdm() != 0)
        {
            instructionSetFlags.AddInstructionSet(InstructionSet_Rdm);
        }

        if (JitConfig.EnableArm64Sha1() != 0)
        {
            instructionSetFlags.AddInstructionSet(InstructionSet_Sha1);
        }

        if (JitConfig.EnableArm64Sha256() != 0)
        {
            instructionSetFlags.AddInstructionSet(InstructionSet_Sha256);
        }

        if (JitConfig.EnableArm64Atomics() != 0)
        {
            instructionSetFlags.AddInstructionSet(InstructionSet_Atomics);
        }

        if (JitConfig.EnableArm64Dczva() != 0)
        {
            instructionSetFlags.AddInstructionSet(InstructionSet_Dczva);
        }

        if (JitConfig.EnableArm64Sve() != 0)
        {
            instructionSetFlags.AddInstructionSet(InstructionSet_Sve);
        }

        if (JitConfig.EnableArm64Sve2() != 0)
        {
            instructionSetFlags.AddInstructionSet(InstructionSet_Sve2);
        }
#elif defined(TARGET_XARCH)
        if (info.compMatchedVM)
        {
            // Keep the existing VectorT* ISAs.
            if (currentInstructionSetFlags.HasInstructionSet(InstructionSet_VectorT128))
            {
                instructionSetFlags.AddInstructionSet(InstructionSet_VectorT128);
            }
            if (currentInstructionSetFlags.HasInstructionSet(InstructionSet_VectorT256))
            {
                instructionSetFlags.AddInstructionSet(InstructionSet_VectorT256);
            }
            if (currentInstructionSetFlags.HasInstructionSet(InstructionSet_VectorT512))
            {
                instructionSetFlags.AddInstructionSet(InstructionSet_VectorT512);
            }
        }

        instructionSetFlags.AddInstructionSet(InstructionSet_X86Base);

        if (JitConfig.EnableAVX() != 0)
        {
            instructionSetFlags.AddInstructionSet(InstructionSet_AVX);
        }

        if (JitConfig.EnableAVX2() != 0)
        {
            instructionSetFlags.AddInstructionSet(InstructionSet_AVX2);
        }

        if (JitConfig.EnableAVX512() != 0)
        {
            instructionSetFlags.AddInstructionSet(InstructionSet_AVX512);
        }

        if (JitConfig.EnableAVX512v2() != 0)
        {
            instructionSetFlags.AddInstructionSet(InstructionSet_AVX512v2);
        }

        if (JitConfig.EnableAVX512v3() != 0)
        {
            instructionSetFlags.AddInstructionSet(InstructionSet_AVX512v3);
        }

        if (JitConfig.EnableAVX10v1() != 0)
        {
            instructionSetFlags.AddInstructionSet(InstructionSet_AVX10v1);
        }

        if (JitConfig.EnableAVX10v2() != 0)
        {
            instructionSetFlags.AddInstructionSet(InstructionSet_AVX10v2);
        }

        if (JitConfig.EnableAPX() != 0)
        {
            instructionSetFlags.AddInstructionSet(InstructionSet_APX);
        }

        if (JitConfig.EnableAES() != 0)
        {
            instructionSetFlags.AddInstructionSet(InstructionSet_AES);

            if (JitConfig.EnableVAES() != 0)
            {
                instructionSetFlags.AddInstructionSet(InstructionSet_AES_V256);
                instructionSetFlags.AddInstructionSet(InstructionSet_AES_V512);
            }
        }

        if (JitConfig.EnableAVX512VP2INTERSECT() != 0)
        {
            instructionSetFlags.AddInstructionSet(InstructionSet_AVX512VP2INTERSECT);
        }

        if (JitConfig.EnableAVXIFMA() != 0)
        {
            instructionSetFlags.AddInstructionSet(InstructionSet_AVXIFMA);
        }

        if (JitConfig.EnableAVXVNNI() != 0)
        {
            instructionSetFlags.AddInstructionSet(InstructionSet_AVXVNNI);
        }

        if (JitConfig.EnableGFNI() != 0)
        {
            instructionSetFlags.AddInstructionSet(InstructionSet_GFNI);
            instructionSetFlags.AddInstructionSet(InstructionSet_GFNI_V256);
            instructionSetFlags.AddInstructionSet(InstructionSet_GFNI_V512);
        }

        if (JitConfig.EnableSHA() != 0)
        {
            instructionSetFlags.AddInstructionSet(InstructionSet_SHA);
        }

        if (JitConfig.EnableWAITPKG() != 0)
        {
            instructionSetFlags.AddInstructionSet(InstructionSet_WAITPKG);
        }

        if (JitConfig.EnableX86Serialize() != 0)
        {
            instructionSetFlags.AddInstructionSet(InstructionSet_X86Serialize);
        }
#elif defined(TARGET_RISCV64)
        instructionSetFlags.AddInstructionSet(InstructionSet_RiscV64Base);

        if (JitConfig.EnableRiscV64Zba() != 0)
        {
            instructionSetFlags.AddInstructionSet(InstructionSet_Zba);
        }

        if (JitConfig.EnableRiscV64Zbb() != 0)
        {
            instructionSetFlags.AddInstructionSet(InstructionSet_Zbb);
        }
#endif

        // These calls are important and explicitly ordered to ensure that the flags are correct in
        // the face of missing or removed instruction sets. Without them, we might end up with incorrect
        // downstream checks.

        instructionSetFlags.Set64BitInstructionSetVariants();
        instructionSetFlags = EnsureInstructionSetFlagsAreValid(instructionSetFlags);

        compileFlags->SetInstructionSetFlags(instructionSetFlags);
    }

    // Set the context for token lookup.
    if (compIsForInlining())
    {
        impTokenLookupContextHandle = impInlineInfo->tokenLookupContextHandle;

        assert(impInlineInfo->inlineCandidateInfo->clsHandle == info.compClassHnd);
        assert(impInlineInfo->inlineCandidateInfo->clsAttr == info.compCompHnd->getClassAttribs(info.compClassHnd));
        // printf("%x != %x\n", impInlineInfo->inlineCandidateInfo->clsAttr,
        // info.compCompHnd->getClassAttribs(info.compClassHnd));
        info.compClassAttr = impInlineInfo->inlineCandidateInfo->clsAttr;
    }
    else
    {
        impTokenLookupContextHandle = METHOD_BEING_COMPILED_CONTEXT();

        info.compClassAttr = info.compCompHnd->getClassAttribs(info.compClassHnd);
    }

#ifdef DEBUG
    if (JitConfig.EnableExtraSuperPmiQueries())
    {
        // Get the assembly name, to aid finding any particular SuperPMI method context function
        (void)eeGetClassAssemblyName(info.compClassHnd);

        // Fetch class names for the method's generic parameters.
        //
        CORINFO_SIG_INFO sig;
        info.compCompHnd->getMethodSig(info.compMethodHnd, &sig, nullptr);

        const unsigned classInst = sig.sigInst.classInstCount;
        if (classInst > 0)
        {
            for (unsigned i = 0; i < classInst; i++)
            {
                eeGetClassName(sig.sigInst.classInst[i]);
            }
        }

        const unsigned methodInst = sig.sigInst.methInstCount;
        if (methodInst > 0)
        {
            for (unsigned i = 0; i < methodInst; i++)
            {
                eeGetClassName(sig.sigInst.methInst[i]);
            }
        }
    }
#endif // DEBUG

#ifdef DEBUG
    if (!compIsForInlining())
    {
        JitTls::GetLogEnv()->setCompiler(this);
    }

    // Have we been told to be more selective in our Jitting?
    if (skipMethod())
    {
        if (compIsForInlining())
        {
            compInlineResult->NoteFatal(InlineObservation::CALLEE_MARKED_AS_SKIPPED);
        }
        return CORJIT_SKIPPED;
    }

#endif // DEBUG

    /* Setup an error trap */

    struct Param
    {
        Compiler* pThis;

        CORINFO_MODULE_HANDLE classPtr;
        COMP_HANDLE           compHnd;
        CORINFO_METHOD_INFO*  methodInfo;
        void**                methodCodePtr;
        uint32_t*             methodCodeSize;
        JitFlags*             compileFlags;

        int result;
    } param;
    param.pThis          = this;
    param.classPtr       = classPtr;
    param.compHnd        = info.compCompHnd;
    param.methodInfo     = info.compMethodInfo;
    param.methodCodePtr  = methodCodePtr;
    param.methodCodeSize = methodCodeSize;
    param.compileFlags   = compileFlags;
    param.result         = CORJIT_INTERNALERROR;

    setErrorTrap(info.compCompHnd, Param*, pParam, &param) // ERROR TRAP: Start normal block
    {
        pParam->result =
            pParam->pThis->compCompileHelper(pParam->classPtr, pParam->compHnd, pParam->methodInfo,
                                             pParam->methodCodePtr, pParam->methodCodeSize, pParam->compileFlags);
    }
    finallyErrorTrap() // ERROR TRAP: The following block handles errors
    {
        /* Cleanup  */

        if (compIsForInlining())
        {
            goto DoneCleanUp;
        }

        /* Tell the emitter that we're done with this function */

        GetEmitter()->emitEndCG();

    DoneCleanUp:
        compDone();
    }
    endErrorTrap() // ERROR TRAP: End

        return param.result;
}

#if defined(DEBUG)
//------------------------------------------------------------------------
// compMethodHash: get hash code for currently jitted method
//
// Returns:
//    Hash based on method's full name
//
unsigned Compiler::Info::compMethodHash() const
{
    if (compMethodHashPrivate == 0)
    {
        // compMethodHashPrivate = compCompHnd->getMethodHash(compMethodHnd);
        assert(compFullName != nullptr);
        assert(*compFullName != 0);
        COUNT_T hash = HashStringA(compFullName); // Use compFullName to generate the hash, as it contains the signature
                                                  // and return type
        compMethodHashPrivate = hash;
    }
    return compMethodHashPrivate;
}

//------------------------------------------------------------------------
// compMethodHash: get hash code for specified method
//
// Arguments:
//    methodHnd - method of interest
//
// Returns:
//    Hash based on method's full name
//
unsigned Compiler::compMethodHash(CORINFO_METHOD_HANDLE methodHnd)
{
    // If this is the root method, delegate to the caching version
    //
    if (methodHnd == info.compMethodHnd)
    {
        return info.compMethodHash();
    }

    // Else compute from scratch. Might consider caching this too.
    //
    unsigned    methodHash = 0;
    const char* calleeName = eeGetMethodFullName(methodHnd);

    if (calleeName != nullptr)
    {
        methodHash = HashStringA(calleeName);
    }
    else
    {
        methodHash = info.compCompHnd->getMethodHash(methodHnd);
    }

    return methodHash;
}

#endif // defined(DEBUG)

void Compiler::compCompileFinish()
{
#if defined(DEBUG) || MEASURE_NODE_SIZE || MEASURE_BLOCK_SIZE || DISPLAY_SIZES || CALL_ARG_STATS
    genMethodCnt++;
#endif

#if MEASURE_MEM_ALLOC
    {
        compArenaAllocator->finishMemStats();
        memAllocHist.record((unsigned)((compArenaAllocator->getTotalBytesAllocated() + 1023) / 1024));
        memUsedHist.record((unsigned)((compArenaAllocator->getTotalBytesUsed() + 1023) / 1024));

        Metrics.BytesAllocated = (int64_t)compArenaAllocator->getTotalBytesUsed();
    }

#ifdef DEBUG
    if (s_dspMemStats || verbose)
    {
        printf("\nAllocations for %s (MethodHash=%08x)\n", info.compFullName, info.compMethodHash());
        compArenaAllocator->dumpMemStats(jitstdout());
    }
#endif // DEBUG
#endif // MEASURE_MEM_ALLOC

#if LOOP_HOIST_STATS
    AddLoopHoistStats();
#endif // LOOP_HOIST_STATS

#if MEASURE_NODE_SIZE
    genTreeNcntHist.record(static_cast<unsigned>(genNodeSizeStatsPerFunc.genTreeNodeCnt));
    genTreeNsizHist.record(static_cast<unsigned>(genNodeSizeStatsPerFunc.genTreeNodeSize));
#endif

#if defined(DEBUG)
    // Small methods should fit in ArenaAllocator::getDefaultPageSize(), or else
    // we should bump up ArenaAllocator::getDefaultPageSize()

    if ((info.compILCodeSize <= 32) &&     // Is it a reasonably small method?
        (info.compNativeCodeSize < 512) && // Some trivial methods generate huge native code. eg. pushing a single huge
                                           // struct
        (impInlinedCodeSize <= 128) &&     // Is the inlining reasonably bounded?
                                           // Small methods cannot meaningfully have a big number of locals
                                           // or arguments. We always track arguments at the start of
                                           // the prolog which requires memory
        (info.compLocalsCount <= 32) && !opts.MinOpts() && // We may have too many local variables, etc
        (getJitStressLevel() == 0) &&                      // We need extra memory for stress
        !opts.optRepeat &&                                 // We need extra memory to repeat opts
        !compArenaAllocator->bypassHostAllocator() && // ArenaAllocator::getDefaultPageSize() is artificially low for
                                                      // DirectAlloc
        // Factor of 2x is because data-structures are bigger under DEBUG
        (compArenaAllocator->getTotalBytesAllocated() > (2 * ArenaAllocator::getDefaultPageSize())) &&
        // RyuJIT backend needs memory tuning! TODO-Cleanup: remove this case when memory tuning is complete.
        (compArenaAllocator->getTotalBytesAllocated() > (10 * ArenaAllocator::getDefaultPageSize())) &&
        !verbose) // We allocate lots of memory to convert sets to strings for JitDump
    {
        genSmallMethodsNeedingExtraMemoryCnt++;

        // Less than 1% of all methods should run into this.
        // We cannot be more strict as there are always degenerate cases where we
        // would need extra memory (like huge structs as locals - see lvaSetStruct()).
        assert((genMethodCnt < 500) || (genSmallMethodsNeedingExtraMemoryCnt < (genMethodCnt / 100)));
    }
#endif // DEBUG

#if defined(DEBUG)

    m_inlineStrategy->DumpData();

    if (JitConfig.JitInlineDumpXmlFile() != nullptr)
    {
        FILE* file = fopen_utf8(JitConfig.JitInlineDumpXmlFile(), "a");
        if (file != nullptr)
        {
            m_inlineStrategy->DumpXml(file);
            fclose(file);
        }
        else
        {
            m_inlineStrategy->DumpXml();
        }
    }
    else
    {
        m_inlineStrategy->DumpXml();
    }

#endif

#ifdef DEBUG
    if (opts.dspOrder)
    {
        // mdMethodDef __stdcall CEEInfo::getMethodDefFromMethod(CORINFO_METHOD_HANDLE hMethod)
        mdMethodDef currentMethodToken = info.compCompHnd->getMethodDefFromMethod(info.compMethodHnd);

        static bool headerPrinted = false;
        if (!headerPrinted)
        {
            // clang-format off
            headerPrinted = true;
            printf("         |  Profiled   | Method   |   Method has    |   calls   | Num |LclV |AProp| CSE |   Perf  |bytes | %3s codesize| \n", Target::g_tgtCPUName);
            printf(" mdToken |  CNT |  RGN |    Hash  | EH | FRM | LOOP | NRM | IND | BBs | Cnt | Cnt | Cnt |  Score  |  IL  |   HOT | CLD | method name \n");
            printf("---------+------+------+----------+----+-----+------+-----+-----+-----+-----+-----+-----+---------+------+-------+-----+\n");
            //      06001234 | 1234 |  HOT | 0f1e2d3c | EH | ebp | LOOP |  15 |   6 |  12 |  17 |  12 |   8 | 1234.56 |  145 |  1234 | 123 | System.Example(int)
            // clang-format on
        }

        printf("%08X | ", currentMethodToken);

        if (fgHaveProfileWeights())
        {
            if (fgCalledCount < 1000)
            {
                printf("%4.0f | ", fgCalledCount);
            }
            else if (fgCalledCount < 1000000)
            {
                printf("%3.0fK | ", fgCalledCount / 1000);
            }
            else
            {
                printf("%3.0fM | ", fgCalledCount / 1000000);
            }
        }
        else
        {
            printf("     | ");
        }

        CorInfoRegionKind regionKind = info.compMethodInfo->regionKind;

        if (opts.altJit)
        {
            printf("ALT | ");
        }
        else if (regionKind == CORINFO_REGION_NONE)
        {
            printf("     | ");
        }
        else if (regionKind == CORINFO_REGION_HOT)
        {
            printf(" HOT | ");
        }
        else if (regionKind == CORINFO_REGION_COLD)
        {
            printf("COLD | ");
        }
        else if (regionKind == CORINFO_REGION_JIT)
        {
            printf(" JIT | ");
        }
        else
        {
            printf("UNKN | ");
        }

        printf("%08x | ", info.compMethodHash());

        if (compHndBBtabCount > 0)
        {
            printf("EH | ");
        }
        else
        {
            printf("   | ");
        }

        if (rpFrameType == FT_EBP_FRAME)
        {
            printf("%3s | ", STR_FPBASE);
        }
        else if (rpFrameType == FT_ESP_FRAME)
        {
            printf("%3s | ", STR_SPBASE);
        }
#if DOUBLE_ALIGN
        else if (rpFrameType == FT_DOUBLE_ALIGN_FRAME)
        {
            printf("dbl | ");
        }
#endif
        else // (rpFrameType == FT_NOT_SET)
        {
            printf("??? | ");
        }

        if (fgHasLoops)
        {
            printf("LOOP |");
        }
        else
        {
            printf("     |");
        }

        printf(" %3d |", optCallCount);
        printf(" %3d |", optIndirectCallCount);
        printf(" %3d |", Metrics.BasicBlocksAtCodegen);
        printf(" %3d |", lvaCount);

        if (opts.MinOpts())
        {
            printf("  MinOpts  |");
        }
        else
        {
            printf(" %3d |", optAssertionCount);
            printf(" %3d |", optCSEcount);
        }

        if (Metrics.PerfScore < 9999.995)
        {
            printf(" %7.2f |", Metrics.PerfScore);
        }
        else
        {
            printf(" %7.0f |", Metrics.PerfScore);
        }

        printf(" %4d |", info.compMethodInfo->ILCodeSize);
        printf(" %5d |", info.compTotalHotCodeSize);
        printf(" %3d |", info.compTotalColdCodeSize);

        printf(" %s\n", eeGetMethodFullName(info.compMethodHnd));
        printf(""); // in our logic this causes a flush
    }

    JITDUMP("Final metrics:\n");
    Metrics.report(this);
    DBEXEC(verbose, Metrics.dump());

    if (verbose)
    {
        printf("\n****** DONE compiling %s\n", info.compFullName);
        printf(""); // in our logic this causes a flush
    }

#if TRACK_ENREG_STATS
    for (unsigned i = 0; i < lvaCount; ++i)
    {
        const LclVarDsc* varDsc = lvaGetDesc(i);

        if (varDsc->lvRefCnt() != 0)
        {
            s_enregisterStats.RecordLocal(varDsc);
        }
    }
#endif // TRACK_ENREG_STATS

    // Only call _DbgBreakCheck when we are jitting, not when we are generating AOT code.
    // For AOT the int3 or breakpoint instruction will be right at the
    // start of the AOT method and we will stop when we execute it.
    //
    if (!IsAot())
    {
        if (compJitHaltMethod())
        {
#if !defined(HOST_UNIX)
            // TODO-UNIX: re-enable this when we have an OS that supports a pop-up dialog

            // Don't do an assert, but just put up the dialog box so we get just-in-time debugger
            // launching.  When you hit 'retry' it will continue and naturally stop at the INT 3
            // that the JIT put in the code
            _DbgBreakCheck(__FILE__, __LINE__, "JitHalt");
#endif
        }
    }
#endif // DEBUG
}

#ifdef PSEUDORANDOM_NOP_INSERTION
// this is zlib adler32 checksum.  source came from windows base

#define BASE 65521L // largest prime smaller than 65536
#define NMAX 5552
// NMAX is the largest n such that 255n(n+1)/2 + (n+1)(BASE-1) <= 2^32-1

#define DO1(buf, i)                                                                                                    \
    {                                                                                                                  \
        s1 += buf[i];                                                                                                  \
        s2 += s1;                                                                                                      \
    }
#define DO2(buf, i)                                                                                                    \
    DO1(buf, i);                                                                                                       \
    DO1(buf, i + 1);
#define DO4(buf, i)                                                                                                    \
    DO2(buf, i);                                                                                                       \
    DO2(buf, i + 2);
#define DO8(buf, i)                                                                                                    \
    DO4(buf, i);                                                                                                       \
    DO4(buf, i + 4);
#define DO16(buf)                                                                                                      \
    DO8(buf, 0);                                                                                                       \
    DO8(buf, 8);

unsigned adler32(unsigned adler, char* buf, unsigned int len)
{
    unsigned int s1 = adler & 0xffff;
    unsigned int s2 = (adler >> 16) & 0xffff;
    int          k;

    if (buf == NULL)
        return 1L;

    while (len > 0)
    {
        k = len < NMAX ? len : NMAX;
        len -= k;
        while (k >= 16)
        {
            DO16(buf);
            buf += 16;
            k -= 16;
        }
        if (k != 0)
            do
            {
                s1 += *buf++;
                s2 += s1;
            } while (--k);
        s1 %= BASE;
        s2 %= BASE;
    }
    return (s2 << 16) | s1;
}
#endif

unsigned getMethodBodyChecksum(_In_z_ char* code, int size)
{
#ifdef PSEUDORANDOM_NOP_INSERTION
    return adler32(0, code, size);
#else
    return 0;
#endif
}

int Compiler::compCompileHelper(CORINFO_MODULE_HANDLE classPtr,
                                COMP_HANDLE           compHnd,
                                CORINFO_METHOD_INFO*  methodInfo,
                                void**                methodCodePtr,
                                uint32_t*             methodCodeSize,
                                JitFlags*             compileFlags)
{
    if (info.compILCodeSize == 0)
    {
        BADCODE("code size is zero");
    }

    if (compIsForInlining())
    {
#ifdef DEBUG
        unsigned methAttr_Old  = impInlineInfo->inlineCandidateInfo->methAttr;
        unsigned methAttr_New  = info.compCompHnd->getMethodAttribs(info.compMethodHnd);
        unsigned flagsToIgnore = CORINFO_FLG_DONT_INLINE | CORINFO_FLG_FORCEINLINE;
        assert((methAttr_Old & (~flagsToIgnore)) == (methAttr_New & (~flagsToIgnore)));
#endif

        info.compFlags    = impInlineInfo->inlineCandidateInfo->methAttr;
        compInlineContext = impInlineInfo->inlineContext;
    }
    else
    {
        info.compFlags = info.compCompHnd->getMethodAttribs(info.compMethodHnd);
#ifdef PSEUDORANDOM_NOP_INSERTION
        info.compChecksum = getMethodBodyChecksum((char*)methodInfo->ILCode, methodInfo->ILCodeSize);
#endif
        compInlineContext = m_inlineStrategy->GetRootContext();
    }

    // compInitOptions will set the correct verbose flag.

    compInitOptions(compileFlags);

    if (!compIsForInlining() && !opts.altJit && opts.jitFlags->IsSet(JitFlags::JIT_FLAG_ALT_JIT))
    {
        // We're an altjit, but the DOTNET_AltJit configuration did not say to compile this method,
        // so skip it.
        return CORJIT_SKIPPED;
    }

#ifdef DEBUG

    if (verbose)
    {
        printf("IL to import:\n");
        dumpILRange(info.compCode, info.compILCodeSize);
    }

#endif

    // Check for DOTNET_AggressiveInlining
    if (JitConfig.JitAggressiveInlining())
    {
        compDoAggressiveInlining = true;
    }

    if (compDoAggressiveInlining)
    {
        info.compFlags |= CORINFO_FLG_FORCEINLINE;
    }

#ifdef DEBUG

    // Check for ForceInline stress.
    if (compStressCompile(STRESS_FORCE_INLINE, 0))
    {
        info.compFlags |= CORINFO_FLG_FORCEINLINE;
    }

    if (compIsForInlining())
    {
        JITLOG((LL_INFO100000, "\nINLINER impTokenLookupContextHandle for %s is 0x%p.\n",
                eeGetMethodFullName(info.compMethodHnd), dspPtr(impTokenLookupContextHandle)));
    }

#endif // DEBUG

    impCanReimport = compStressCompile(STRESS_CHK_REIMPORT, 15);

    /* Initialize set a bunch of global values */

    info.compScopeHnd    = classPtr;
    info.compXcptnsCount = methodInfo->EHcount;
    info.compMaxStack    = methodInfo->maxStack;

    /* Initialize emitter */

    if (!compIsForInlining())
    {
        codeGen->GetEmitter()->emitBegCG(this, compHnd);
    }

    info.compIsStatic = (info.compFlags & CORINFO_FLG_STATIC) != 0;

    info.compPublishStubParam = opts.jitFlags->IsSet(JitFlags::JIT_FLAG_PUBLISH_SECRET_PARAM);

    if (opts.IsReversePInvoke())
    {
        bool unused;
        info.compCallConv = info.compCompHnd->getUnmanagedCallConv(methodInfo->ftn, nullptr, &unused);
        info.compArgOrder = Target::g_tgtUnmanagedArgOrder;
    }
    else
    {
        info.compCallConv = CorInfoCallConvExtension::Managed;
        info.compArgOrder = Target::g_tgtArgOrder;
    }

    switch (methodInfo->args.getCallConv())
    {
        case CORINFO_CALLCONV_NATIVEVARARG:
        case CORINFO_CALLCONV_VARARG:
            info.compIsVarArgs = true;
            break;
        default:
            break;
    }

    info.compRetType = JITtype2varType(methodInfo->args.retType);
    if (info.compRetType == TYP_STRUCT)
    {
        info.compRetType = impNormStructType(methodInfo->args.retTypeClass);
    }

    info.compInitMem = ((methodInfo->options & CORINFO_OPT_INIT_LOCALS) != 0);

    /* Allocate the local variable table */

    lvaInitTypeRef();

    compInitDebuggingInfo();

    // If are an altjit and have patchpoint info, we might need to tweak the frame size
    // so it's plausible for the altjit architecture.
    //
    if (!info.compMatchedVM && compileFlags->IsSet(JitFlags::JIT_FLAG_OSR))
    {
        assert(info.compLocalsCount == info.compPatchpointInfo->NumberOfLocals());
        const int totalFrameSize = info.compPatchpointInfo->TotalFrameSize();

        int frameSizeUpdate = 0;

#if defined(TARGET_AMD64)
        if ((totalFrameSize % 16) != 8)
        {
            frameSizeUpdate = 8;
        }
#elif defined(TARGET_ARM64) || defined(TARGET_LOONGARCH64) || defined(TARGET_RISCV64)
        if ((totalFrameSize & 0xf) != 0)
        {
            frameSizeUpdate = 8;
        }
#endif
        if (frameSizeUpdate != 0)
        {
            JITDUMP("Mismatched altjit + OSR -- updating tier0 frame size from %d to %d\n", totalFrameSize,
                    totalFrameSize + frameSizeUpdate);

            // Allocate a local copy with altered frame size.
            //
            const unsigned        patchpointInfoSize = PatchpointInfo::ComputeSize(info.compLocalsCount);
            PatchpointInfo* const newInfo =
                (PatchpointInfo*)getAllocator(CMK_Unknown).allocate<char>(patchpointInfoSize);

            newInfo->Initialize(info.compLocalsCount, totalFrameSize + frameSizeUpdate);
            newInfo->Copy(info.compPatchpointInfo);

            // Swap it in place.
            //
            info.compPatchpointInfo = newInfo;
        }
    }

    if (compIsForInlining())
    {
        compBasicBlockID = impInlineInfo->InlinerCompiler->compBasicBlockID;
    }

    const bool forceInline = !!(info.compFlags & CORINFO_FLG_FORCEINLINE);

    if (!compIsForInlining() && IsAot())
    {
        // We're AOT compiling the root method. We also will analyze it as
        // a potential inline candidate.
        InlineResult prejitResult(this, info.compMethodHnd, "prejit");

        // Profile data allows us to avoid early "too many IL bytes" outs.
        prejitResult.NoteBool(InlineObservation::CALLSITE_HAS_PROFILE_WEIGHTS, fgHaveSufficientProfileWeights());

        // Do the initial inline screen.
        impCanInlineIL(info.compMethodHnd, methodInfo, forceInline, &prejitResult);

        // Temporarily install the prejitResult as the
        // compInlineResult so it's available to fgFindJumpTargets
        // and can accumulate more observations as the IL is
        // scanned.
        //
        // We don't pass prejitResult in as a parameter to avoid
        // potential aliasing confusion -- the other call to
        // fgFindBasicBlocks may have set up compInlineResult and
        // the code in fgFindJumpTargets references that data
        // member extensively.
        assert(compInlineResult == nullptr);
        assert(impInlineInfo == nullptr);
        compInlineResult = &prejitResult;

        // Find the basic blocks. We must do this regardless of
        // inlineability, since we are prejitting this method.
        //
        // This will also update the status of this method as
        // an inline candidate.
        fgFindBasicBlocks();

        // Undo the temporary setup.
        assert(compInlineResult == &prejitResult);
        compInlineResult = nullptr;

        // If still a viable, discretionary inline, assess
        // profitability.
        if (prejitResult.IsDiscretionaryCandidate())
        {
            prejitResult.DetermineProfitability(methodInfo);
        }

        m_inlineStrategy->NotePrejitDecision(prejitResult);

        // Handle the results of the inline analysis.
        if (prejitResult.IsFailure())
        {
            // This method is a bad inlinee according to our
            // analysis.  We will let the InlineResult destructor
            // mark it as noinline in the prejit image to save the
            // jit some work.
            //
            // This decision better not be context-dependent.
            assert(prejitResult.IsNever());
        }
        else
        {
            // This looks like a viable inline candidate.  Since
            // we're not actually inlining, don't report anything.
            prejitResult.SetSuccessResult(INLINE_PREJIT_SUCCESS);
        }
    }
    else
    {
        // We are jitting the root method, or inlining.
        fgFindBasicBlocks();
    }

    // If we're inlining and the candidate is bad, bail out.
    if (compDonotInline())
    {
        goto _Next;
    }

    // We may decide to optimize this method,
    // to avoid spending a long time stuck in Tier0 code.
    //
    if (fgCanSwitchToOptimized())
    {
        // We only expect to be able to do this at Tier0.
        //
        assert(opts.jitFlags->IsSet(JitFlags::JIT_FLAG_TIER0));

        // Normal tiering should bail us out of Tier0 tail call induced loops.
        // So keep these methods in Tier0 if we're gathering PGO data.
        // If we're not gathering PGO, then switch these to optimized to
        // minimize the number of tail call helper stubs we might need.
        // Reconsider this if/when we're able to share those stubs.
        //
        // Honor the config setting that tells the jit to
        // always optimize methods with loops.
        //
        // If neither of those apply, and OSR is enabled, the jit may still
        // decide to optimize, if there's something in the method that
        // OSR currently cannot handle, or we're optionally suppressing
        // OSR by method hash.
        //
        const char* reason = nullptr;

        if (compTailPrefixSeen && !opts.jitFlags->IsSet(JitFlags::JIT_FLAG_BBINSTR))
        {
            reason = "tail.call and not BBINSTR";
        }
        else if (compHasBackwardJump && ((info.compFlags & CORINFO_FLG_DISABLE_TIER0_FOR_LOOPS) != 0))
        {
            reason = "loop";
        }

        if (compHasBackwardJump && (reason == nullptr) && (JitConfig.TC_OnStackReplacement() > 0))
        {
            bool canEscapeViaOSR = compCanHavePatchpoints(&reason);

#ifdef DEBUG
            if (canEscapeViaOSR)
            {
                // Optionally disable OSR by method hash. This will force any
                // method that might otherwise get trapped in Tier0 to be optimized.
                //
                static ConfigMethodRange JitEnableOsrRange;
                JitEnableOsrRange.EnsureInit(JitConfig.JitEnableOsrRange());
                const unsigned hash = impInlineRoot()->info.compMethodHash();
                if (!JitEnableOsrRange.Contains(hash))
                {
                    canEscapeViaOSR = false;
                    reason          = "OSR disabled by JitEnableOsrRange";
                }
            }
#endif

            if (canEscapeViaOSR)
            {
                JITDUMP("\nOSR enabled for this method\n");
                if (compHasBackwardJump && !compTailPrefixSeen &&
                    opts.jitFlags->IsSet(JitFlags::JIT_FLAG_BBINSTR_IF_LOOPS) && opts.IsTier0())
                {
                    assert((info.compFlags & CORINFO_FLG_DISABLE_TIER0_FOR_LOOPS) == 0);
                    opts.jitFlags->Set(JitFlags::JIT_FLAG_BBINSTR);
                    JITDUMP("\nEnabling instrumentation for this method so OSR'd version will have a profile.\n");
                }
            }
            else
            {
                JITDUMP("\nOSR disabled for this method: %s\n", reason);
                assert(reason != nullptr);
            }
        }

        if (reason != nullptr)
        {
            fgSwitchToOptimized(reason);
        }
    }

    compSetOptimizationLevel();

#ifdef DEBUG
    if (JitConfig.JitInstrumentIfOptimizing() && opts.OptimizationEnabled() && !IsReadyToRun())
    {
        // Optionally disable by range
        //
        static ConfigMethodRange JitInstrumentIfOptimizingRange;
        JitInstrumentIfOptimizingRange.EnsureInit(JitConfig.JitInstrumentIfOptimizingRange());
        const unsigned hash = impInlineRoot()->info.compMethodHash();
        if (JitInstrumentIfOptimizingRange.Contains(hash))
        {
            JITDUMP("\nEnabling instrumentation\n");
            opts.jitFlags->Set(JitFlags::JIT_FLAG_BBINSTR);
        }
    }
#endif

    if ((JitConfig.JitDisasmOnlyOptimized() != 0) && (!opts.OptimizationEnabled()))
    {
        // Disable JitDisasm for non-optimized code.
        opts.disAsm = false;
    }

#if COUNT_BASIC_BLOCKS
    bbCntTable.record(fgBBcount);

    if (fgBBcount == 1)
    {
        bbOneBBSizeTable.record(methodInfo->ILCodeSize);
    }
#endif // COUNT_BASIC_BLOCKS

#ifdef DEBUG
    if (verbose)
    {
        printf("Basic block list for '%s'\n", info.compFullName);
        fgDispBasicBlocks();
    }
#endif

#ifdef DEBUG
    /* Give the function a unique number */

    if (opts.disAsm || verbose)
    {
        compMethodID = ~info.compMethodHash() & 0xffff;
    }
    else
    {
        compMethodID = InterlockedIncrement(&s_compMethodsCount);
    }
#endif

    if (compIsForInlining())
    {
        compInlineResult->NoteInt(InlineObservation::CALLEE_NUMBER_OF_BASIC_BLOCKS, fgBBcount);

        if (compInlineResult->IsFailure())
        {
            goto _Next;
        }
    }

#ifdef DEBUG
    if (compIsForInlining())
    {
        compGenTreeID   = impInlineInfo->InlinerCompiler->compGenTreeID;
        compStatementID = impInlineInfo->InlinerCompiler->compStatementID;
    }
#endif

    compCompile(methodCodePtr, methodCodeSize, compileFlags);

#ifdef DEBUG
    if (compIsForInlining())
    {
        impInlineInfo->InlinerCompiler->compGenTreeID   = compGenTreeID;
        impInlineInfo->InlinerCompiler->compStatementID = compStatementID;
    }
#endif

    if (compIsForInlining())
    {
        impInlineInfo->InlinerCompiler->compBasicBlockID = compBasicBlockID;
    }

_Next:

    if (compDonotInline())
    {
        // Verify we have only one inline result in play.
        assert(impInlineInfo->inlineResult == compInlineResult);
    }

    if (!compIsForInlining())
    {
        compCompileFinish();

        // Did we just compile for a target architecture that the VM isn't expecting? If so, the VM
        // can't used the generated code (and we better be an AltJit!).

        if (!info.compMatchedVM)
        {
            return CORJIT_SKIPPED;
        }

#ifdef DEBUG
        if (opts.jitFlags->IsSet(JitFlags::JIT_FLAG_ALT_JIT) && JitConfig.RunAltJitCode() == 0)
        {
            return CORJIT_SKIPPED;
        }
#endif // DEBUG
    }

    /* Success! */
    return CORJIT_OK;
}

//------------------------------------------------------------------------
// compFindLocalVarLinear: Linear search for variable's scope containing offset.
//
// Arguments:
//     varNum    The variable number to search for in the array of scopes.
//     offs      The offset value which should occur within the life of the variable.
//
// Return Value:
//     VarScopeDsc* of a matching variable that contains the offset within its life
//     begin and life end or nullptr when there is no match found.
//
//  Description:
//     Linear search for matching variables with their life begin and end containing
//     the offset.
//     or NULL if one couldn't be found.
//
//  Note:
//     Usually called for scope count = 4. Could be called for values upto 8.
//
VarScopeDsc* Compiler::compFindLocalVarLinear(unsigned varNum, unsigned offs)
{
    for (unsigned i = 0; i < info.compVarScopesCount; i++)
    {
        VarScopeDsc* dsc = &info.compVarScopes[i];
        if ((dsc->vsdVarNum == varNum) && (dsc->vsdLifeBeg <= offs) && (dsc->vsdLifeEnd > offs))
        {
            return dsc;
        }
    }
    return nullptr;
}

//------------------------------------------------------------------------
// compFindLocalVar: Search for variable's scope containing offset.
//
// Arguments:
//    varNum    The variable number to search for in the array of scopes.
//    offs      The offset value which should occur within the life of the variable.
//
// Return Value:
//    VarScopeDsc* of a matching variable that contains the offset within its life
//    begin and life end.
//    or NULL if one couldn't be found.
//
//  Description:
//     Linear search for matching variables with their life begin and end containing
//     the offset only when the scope count is < MAX_LINEAR_FIND_LCL_SCOPELIST,
//     else use the hashtable lookup.
//
VarScopeDsc* Compiler::compFindLocalVar(unsigned varNum, unsigned offs)
{
    if (info.compVarScopesCount < MAX_LINEAR_FIND_LCL_SCOPELIST)
    {
        return compFindLocalVarLinear(varNum, offs);
    }
    else
    {
        VarScopeDsc* ret = compFindLocalVar(varNum, offs, offs);
        assert(ret == compFindLocalVarLinear(varNum, offs));
        return ret;
    }
}

//------------------------------------------------------------------------
// compFindLocalVar: Search for variable's scope containing offset.
//
// Arguments:
//    varNum    The variable number to search for in the array of scopes.
//    lifeBeg   The life begin of the variable's scope
//    lifeEnd   The life end of the variable's scope
//
// Return Value:
//    VarScopeDsc* of a matching variable that contains the offset within its life
//    begin and life end, or NULL if one couldn't be found.
//
//  Description:
//     Following are the steps used:
//     1. Index into the hashtable using varNum.
//     2. Iterate through the linked list at index varNum to find a matching
//        var scope.
//
VarScopeDsc* Compiler::compFindLocalVar(unsigned varNum, unsigned lifeBeg, unsigned lifeEnd)
{
    assert(compVarScopeMap != nullptr);

    VarScopeMapInfo* info;
    if (compVarScopeMap->Lookup(varNum, &info))
    {
        VarScopeListNode* list = info->head;
        while (list != nullptr)
        {
            if ((list->data->vsdLifeBeg <= lifeBeg) && (list->data->vsdLifeEnd > lifeEnd))
            {
                return list->data;
            }
            list = list->next;
        }
    }
    return nullptr;
}

//-------------------------------------------------------------------------
// compInitVarScopeMap: Create a scope map so it can be looked up by varNum
//
//  Description:
//     Map.K => Map.V :: varNum => List(ScopeDsc)
//
//     Create a scope map that can be indexed by varNum and can be iterated
//     on it's values to look for matching scope when given an offs or
//     lifeBeg and lifeEnd.
//
//  Notes:
//     1. Build the map only when we think linear search is slow, i.e.,
//     MAX_LINEAR_FIND_LCL_SCOPELIST is large.
//     2. Linked list preserves original array order.
//
void Compiler::compInitVarScopeMap()
{
    if (info.compVarScopesCount < MAX_LINEAR_FIND_LCL_SCOPELIST)
    {
        return;
    }

    assert(compVarScopeMap == nullptr);

    compVarScopeMap = new (getAllocator()) VarNumToScopeDscMap(getAllocator());

    // 599 prime to limit huge allocations; for ex: duplicated scopes on single var.
    compVarScopeMap->Reallocate(min(info.compVarScopesCount, 599u));

    for (unsigned i = 0; i < info.compVarScopesCount; ++i)
    {
        unsigned varNum = info.compVarScopes[i].vsdVarNum;

        VarScopeListNode* node = VarScopeListNode::Create(&info.compVarScopes[i], getAllocator());

        // Index by varNum and if the list exists append "node" to the "list".
        VarScopeMapInfo* info;
        if (compVarScopeMap->Lookup(varNum, &info))
        {
            info->tail->next = node;
            info->tail       = node;
        }
        // Create a new list.
        else
        {
            info = VarScopeMapInfo::Create(node, getAllocator());
            compVarScopeMap->Set(varNum, info);
        }
    }
}

struct genCmpLocalVarLifeBeg
{
    bool operator()(const VarScopeDsc* elem1, const VarScopeDsc* elem2)
    {
        return elem1->vsdLifeBeg < elem2->vsdLifeBeg;
    }
};

struct genCmpLocalVarLifeEnd
{
    bool operator()(const VarScopeDsc* elem1, const VarScopeDsc* elem2)
    {
        return elem1->vsdLifeEnd < elem2->vsdLifeEnd;
    }
};

inline void Compiler::compInitScopeLists()
{
    if (info.compVarScopesCount == 0)
    {
        compEnterScopeList = compExitScopeList = nullptr;
        return;
    }

    // Populate the 'compEnterScopeList' and 'compExitScopeList' lists

    compEnterScopeList = new (this, CMK_DebugInfo) VarScopeDsc*[info.compVarScopesCount];
    compExitScopeList  = new (this, CMK_DebugInfo) VarScopeDsc*[info.compVarScopesCount];

    for (unsigned i = 0; i < info.compVarScopesCount; i++)
    {
        compEnterScopeList[i] = compExitScopeList[i] = &info.compVarScopes[i];
    }

    jitstd::sort(compEnterScopeList, compEnterScopeList + info.compVarScopesCount, genCmpLocalVarLifeBeg());
    jitstd::sort(compExitScopeList, compExitScopeList + info.compVarScopesCount, genCmpLocalVarLifeEnd());
}

void Compiler::compResetScopeLists()
{
    if (info.compVarScopesCount == 0)
    {
        return;
    }

    assert(compEnterScopeList && compExitScopeList);

    compNextEnterScope = compNextExitScope = 0;
}

VarScopeDsc* Compiler::compGetNextEnterScope(unsigned offs, bool scan)
{
    assert(info.compVarScopesCount);
    assert(compEnterScopeList && compExitScopeList);

    if (compNextEnterScope < info.compVarScopesCount)
    {
        assert(compEnterScopeList[compNextEnterScope]);
        unsigned nextEnterOff = compEnterScopeList[compNextEnterScope]->vsdLifeBeg;
        assert(scan || (offs <= nextEnterOff));

        if (!scan)
        {
            if (offs == nextEnterOff)
            {
                return compEnterScopeList[compNextEnterScope++];
            }
        }
        else
        {
            if (nextEnterOff <= offs)
            {
                return compEnterScopeList[compNextEnterScope++];
            }
        }
    }

    return nullptr;
}

VarScopeDsc* Compiler::compGetNextExitScope(unsigned offs, bool scan)
{
    assert(info.compVarScopesCount);
    assert(compEnterScopeList && compExitScopeList);

    if (compNextExitScope < info.compVarScopesCount)
    {
        assert(compExitScopeList[compNextExitScope]);
        unsigned nextExitOffs = compExitScopeList[compNextExitScope]->vsdLifeEnd;
        assert(scan || (offs <= nextExitOffs));

        if (!scan)
        {
            if (offs == nextExitOffs)
            {
                return compExitScopeList[compNextExitScope++];
            }
        }
        else
        {
            if (nextExitOffs <= offs)
            {
                return compExitScopeList[compNextExitScope++];
            }
        }
    }

    return nullptr;
}

// The function will call the callback functions for scopes with boundaries
// at instrs from the current status of the scope lists to 'offset',
// ordered by instrs.

void Compiler::compProcessScopesUntil(unsigned   offset,
                                      VARSET_TP* inScope,
                                      void (Compiler::*enterScopeFn)(VARSET_TP* inScope, VarScopeDsc*),
                                      void (Compiler::*exitScopeFn)(VARSET_TP* inScope, VarScopeDsc*))
{
    assert(offset != BAD_IL_OFFSET);
    assert(inScope != nullptr);

    bool         foundExit = false, foundEnter = true;
    VarScopeDsc* scope;
    VarScopeDsc* nextExitScope  = nullptr;
    VarScopeDsc* nextEnterScope = nullptr;
    unsigned     offs = offset, curEnterOffs = 0;

    goto START_FINDING_SCOPES;

    // We need to determine the scopes which are open for the current block.
    // This loop walks over the missing blocks between the current and the
    // previous block, keeping the enter and exit offsets in lockstep.

    do
    {
        foundExit = foundEnter = false;

        if (nextExitScope)
        {
            (this->*exitScopeFn)(inScope, nextExitScope);
            nextExitScope = nullptr;
            foundExit     = true;
        }

        offs = nextEnterScope ? nextEnterScope->vsdLifeBeg : offset;

        while ((scope = compGetNextExitScope(offs, true)) != nullptr)
        {
            foundExit = true;

            if (!nextEnterScope || scope->vsdLifeEnd > nextEnterScope->vsdLifeBeg)
            {
                // We overshot the last found Enter scope. Save the scope for later
                // and find an entering scope

                nextExitScope = scope;
                break;
            }

            (this->*exitScopeFn)(inScope, scope);
        }

        if (nextEnterScope)
        {
            (this->*enterScopeFn)(inScope, nextEnterScope);
            curEnterOffs   = nextEnterScope->vsdLifeBeg;
            nextEnterScope = nullptr;
            foundEnter     = true;
        }

        offs = nextExitScope ? nextExitScope->vsdLifeEnd : offset;

    START_FINDING_SCOPES:

        while ((scope = compGetNextEnterScope(offs, true)) != nullptr)
        {
            foundEnter = true;

            if ((nextExitScope && scope->vsdLifeBeg >= nextExitScope->vsdLifeEnd) || (scope->vsdLifeBeg > curEnterOffs))
            {
                // We overshot the last found exit scope. Save the scope for later
                // and find an exiting scope

                nextEnterScope = scope;
                break;
            }

            (this->*enterScopeFn)(inScope, scope);

            if (!nextExitScope)
            {
                curEnterOffs = scope->vsdLifeBeg;
            }
        }
    } while (foundExit || foundEnter);
}

#if defined(DEBUG)

void Compiler::compDispScopeLists()
{
    unsigned i;

    printf("Local variable scopes = %d\n", info.compVarScopesCount);

    if (info.compVarScopesCount)
    {
        printf("    \tVarNum \tLVNum \t      Name \tBeg \tEnd\n");
    }

    printf("Sorted by enter scope:\n");
    for (i = 0; i < info.compVarScopesCount; i++)
    {
        VarScopeDsc* varScope = compEnterScopeList[i];
        assert(varScope);
        printf("%2d: \t%02Xh \t%02Xh \t%10s \t%03Xh   \t%03Xh", i, varScope->vsdVarNum, varScope->vsdLVnum,
               VarNameToStr(varScope->vsdName) == nullptr ? "UNKNOWN" : VarNameToStr(varScope->vsdName),
               varScope->vsdLifeBeg, varScope->vsdLifeEnd);

        if (compNextEnterScope == i)
        {
            printf(" <-- next enter scope");
        }

        printf("\n");
    }

    printf("Sorted by exit scope:\n");
    for (i = 0; i < info.compVarScopesCount; i++)
    {
        VarScopeDsc* varScope = compExitScopeList[i];
        assert(varScope);
        printf("%2d: \t%02Xh \t%02Xh \t%10s \t%03Xh   \t%03Xh", i, varScope->vsdVarNum, varScope->vsdLVnum,
               VarNameToStr(varScope->vsdName) == nullptr ? "UNKNOWN" : VarNameToStr(varScope->vsdName),
               varScope->vsdLifeBeg, varScope->vsdLifeEnd);

        if (compNextExitScope == i)
        {
            printf(" <-- next exit scope");
        }

        printf("\n");
    }
}

void Compiler::compDispLocalVars()
{
    printf("info.compVarScopesCount = %d\n", info.compVarScopesCount);

    if (info.compVarScopesCount > 0)
    {
        printf("    \tVarNum \tLVNum \t      Name \tBeg \tEnd\n");
    }

    for (unsigned i = 0; i < info.compVarScopesCount; i++)
    {
        VarScopeDsc* varScope = &info.compVarScopes[i];
        printf("%2d: \t%02Xh \t%02Xh \t%10s \t%03Xh   \t%03Xh\n", i, varScope->vsdVarNum, varScope->vsdLVnum,
               VarNameToStr(varScope->vsdName) == nullptr ? "UNKNOWN" : VarNameToStr(varScope->vsdName),
               varScope->vsdLifeBeg, varScope->vsdLifeEnd);
    }
}

#endif // DEBUG

/*****************************************************************************/

#if MEASURE_CLRAPI_CALLS

struct WrapICorJitInfo : public ICorJitInfo
{
    //------------------------------------------------------------------------
    // WrapICorJitInfo::makeOne: allocate an instance of WrapICorJitInfo
    //
    // Arguments:
    //    alloc      - the allocator to get memory from for the instance
    //    compile    - the compiler instance
    //    compHndRef - the ICorJitInfo handle from the EE; the caller's
    //                 copy may be replaced with a "wrapper" instance
    //
    // Return Value:
    //    If the config flags indicate that ICorJitInfo should be wrapped,
    //    we return the "wrapper" instance; otherwise we return "nullptr".

    static WrapICorJitInfo* makeOne(ArenaAllocator* alloc, Compiler* compiler, COMP_HANDLE& compHndRef /* INOUT */)
    {
        WrapICorJitInfo* wrap = nullptr;

        if (JitConfig.JitEECallTimingInfo() != 0)
        {
            // It's too early to use the default allocator, so we do this
            // in two steps to be safe (the constructor doesn't need to do
            // anything except fill in the vtable pointer, so we let the
            // compiler do it).
            void* inst = alloc->allocateMemory(roundUp(sizeof(WrapICorJitInfo)));
            if (inst != nullptr)
            {
                // If you get a build error here due to 'WrapICorJitInfo' being
                // an abstract class, it's very likely that the wrapper bodies
                // in ICorJitInfo_wrapper_generated.hpp are no longer in sync with
                // the EE interface; please be kind and update the header file.
                wrap = new (inst, jitstd::placement_t()) WrapICorJitInfo();

                wrap->wrapComp = compiler;

                // Save the real handle and replace it with our wrapped version.
                wrap->wrapHnd = compHndRef;
                compHndRef    = wrap;
            }
        }

        return wrap;
    }

private:
    Compiler*   wrapComp;
    COMP_HANDLE wrapHnd; // the "real thing"

public:
#include "ICorJitInfo_wrapper_generated.hpp"
};

#endif // MEASURE_CLRAPI_CALLS

/*****************************************************************************/

// Compile a single method

int jitNativeCode(CORINFO_METHOD_HANDLE methodHnd,
                  CORINFO_MODULE_HANDLE classPtr,
                  COMP_HANDLE           compHnd,
                  CORINFO_METHOD_INFO*  methodInfo,
                  void**                methodCodePtr,
                  uint32_t*             methodCodeSize,
                  JitFlags*             compileFlags,
                  void*                 inlineInfoPtr)
{
    //
    // A non-NULL inlineInfo means we are compiling the inlinee method.
    //
    InlineInfo* inlineInfo = (InlineInfo*)inlineInfoPtr;

    bool jitFallbackCompile = false;
START:
    int result = CORJIT_INTERNALERROR;

    ArenaAllocator* pAlloc = nullptr;
    ArenaAllocator  alloc;

#if MEASURE_CLRAPI_CALLS
    WrapICorJitInfo* wrapCLR = nullptr;
#endif

    if (inlineInfo)
    {
        // Use inliner's memory allocator when compiling the inlinee.
        pAlloc = inlineInfo->InlinerCompiler->compGetArenaAllocator();
    }
    else
    {
        pAlloc = &alloc;
    }

    Compiler* pComp;
    pComp = nullptr;

    struct Param
    {
        Compiler*       pComp;
        Compiler*       pPrevComp;
        ArenaAllocator* pAlloc;
        bool            jitFallbackCompile;

        CORINFO_METHOD_HANDLE methodHnd;
        CORINFO_MODULE_HANDLE classPtr;
        COMP_HANDLE           compHnd;
        CORINFO_METHOD_INFO*  methodInfo;
        void**                methodCodePtr;
        uint32_t*             methodCodeSize;
        JitFlags*             compileFlags;
        InlineInfo*           inlineInfo;
#if MEASURE_CLRAPI_CALLS
        WrapICorJitInfo* wrapCLR;
#endif

        int result;
    } param;
    param.pComp              = nullptr;
    param.pPrevComp          = nullptr;
    param.pAlloc             = pAlloc;
    param.jitFallbackCompile = jitFallbackCompile;
    param.methodHnd          = methodHnd;
    param.classPtr           = classPtr;
    param.compHnd            = compHnd;
    param.methodInfo         = methodInfo;
    param.methodCodePtr      = methodCodePtr;
    param.methodCodeSize     = methodCodeSize;
    param.compileFlags       = compileFlags;
    param.inlineInfo         = inlineInfo;
#if MEASURE_CLRAPI_CALLS
    param.wrapCLR = nullptr;
#endif
    param.result = result;

    // clang-format off
    setErrorTrap(compHnd, Param*, pParamOuter, &param)
    {
        setErrorTrap(nullptr, Param*, pParam, pParamOuter)
        {
            void* compilerMem;
            if (pParam->inlineInfo)
            {
                // Lazily create the inlinee compiler object
                if (pParam->inlineInfo->InlinerCompiler->InlineeCompiler == nullptr)
                {
                    compilerMem = pParam->pAlloc->allocateMemory(sizeof(Compiler));
                    pParam->inlineInfo->InlinerCompiler->InlineeCompiler = static_cast<Compiler*>(compilerMem);
                }
                else
                {
                    compilerMem = pParam->inlineInfo->InlinerCompiler->InlineeCompiler;
                }
            }
            else
            {
                compilerMem = pParam->pAlloc->allocateMemory(sizeof(Compiler));
            }

            pParam->pComp = new (compilerMem, jitstd::placement_t()) Compiler(pParam->pAlloc, pParam->methodHnd, pParam->compHnd, pParam->methodInfo, pParam->inlineInfo);

#if MEASURE_CLRAPI_CALLS
            pParam->wrapCLR = WrapICorJitInfo::makeOne(pParam->pAlloc, pParam->pComp, pParam->compHnd);
#endif

            // push this compiler on the stack (TLS)
            pParam->pPrevComp = JitTls::GetCompiler();
            JitTls::SetCompiler(pParam->pComp);

            assert(pParam->pComp != nullptr);

#ifdef DEBUG
            pParam->pComp->jitFallbackCompile = pParam->jitFallbackCompile;
#endif

            // Now generate the code
            pParam->result =
                pParam->pComp->compCompileAfterInit(pParam->classPtr, pParam->methodCodePtr, pParam->methodCodeSize, pParam->compileFlags);
        }
        finallyErrorTrap()
        {
            Compiler* pCompiler = pParamOuter->pComp;

            // If OOM is thrown when allocating memory for a pComp, we will end up here.
            // For this case, pComp and also pCompiler will be a nullptr
            //
            if (pCompiler != nullptr)
            {
                pCompiler->info.compCode = nullptr;

                // pop the compiler off the TLS stack only if it was linked above
                assert(JitTls::GetCompiler() == pCompiler);
                JitTls::SetCompiler(pParamOuter->pPrevComp);
            }

            if (pParamOuter->inlineInfo == nullptr)
            {
                // Free up the allocator we were using
                pParamOuter->pAlloc->destroy();
            }
        }
        endErrorTrap()
    }
    impJitErrorTrap()
    {
        // If we were looking at an inlinee....
        if (inlineInfo != nullptr)
        {
            // Note that we failed to compile the inlinee, and that
            // there's no point trying to inline it again anywhere else.
            inlineInfo->inlineResult->NoteFatal(InlineObservation::CALLEE_COMPILATION_ERROR);
        }
        param.result = __errc;
    }
    endErrorTrap()
    // clang-format on

    result = param.result;

if (!inlineInfo &&
    (result == CORJIT_INTERNALERROR || result == CORJIT_RECOVERABLEERROR || result == CORJIT_IMPLLIMITATION) &&
    !jitFallbackCompile)
{
    // If we failed the JIT, reattempt with debuggable code.
    jitFallbackCompile = true;

    // Update the flags for 'safer' code generation.
    compileFlags->Set(JitFlags::JIT_FLAG_MIN_OPT);
    compileFlags->Clear(JitFlags::JIT_FLAG_SIZE_OPT);
    compileFlags->Clear(JitFlags::JIT_FLAG_SPEED_OPT);
    compileFlags->Clear(JitFlags::JIT_FLAG_BBOPT);

    goto START;
}

return result;
}

#if defined(UNIX_AMD64_ABI)

// GetTypeFromClassificationAndSizes:
//   Returns the type of the eightbyte accounting for the classification and size of the eightbyte.
//
// args:
//   classType: classification type
//   size: size of the eightbyte.
//
// static
var_types Compiler::GetTypeFromClassificationAndSizes(SystemVClassificationType classType, int size)
{
    var_types type = TYP_UNKNOWN;
    switch (classType)
    {
        case SystemVClassificationTypeInteger:
            if (size == 1)
            {
                type = TYP_BYTE;
            }
            else if (size <= 2)
            {
                type = TYP_SHORT;
            }
            else if (size <= 4)
            {
                type = TYP_INT;
            }
            else if (size <= 8)
            {
                type = TYP_LONG;
            }
            else
            {
                assert(false && "GetTypeFromClassificationAndSizes Invalid Integer classification type.");
            }
            break;
        case SystemVClassificationTypeIntegerReference:
            type = TYP_REF;
            break;
        case SystemVClassificationTypeIntegerByRef:
            type = TYP_BYREF;
            break;
        case SystemVClassificationTypeSSE:
            if (size <= 4)
            {
                type = TYP_FLOAT;
            }
            else if (size <= 8)
            {
                type = TYP_DOUBLE;
            }
            else
            {
                assert(false && "GetTypeFromClassificationAndSizes Invalid SSE classification type.");
            }
            break;

        default:
            assert(false && "GetTypeFromClassificationAndSizes Invalid classification type.");
            break;
    }

    return type;
}

//-------------------------------------------------------------------
// GetEightByteType: Returns the type of eightbyte slot of a struct
//
// Arguments:
//   structDesc  -  struct classification description.
//   slotNum     -  eightbyte slot number for the struct.
//
// Return Value:
//    type of the eightbyte slot of the struct
//
// static
var_types Compiler::GetEightByteType(const SYSTEMV_AMD64_CORINFO_STRUCT_REG_PASSING_DESCRIPTOR& structDesc,
                                     unsigned                                                   slotNum)
{
    var_types eightByteType = TYP_UNDEF;
    unsigned  len           = structDesc.eightByteSizes[slotNum];

    switch (structDesc.eightByteClassifications[slotNum])
    {
        case SystemVClassificationTypeInteger:
            // See typelist.h for jit type definition.
            // All the types of size < 4 bytes are of jit type TYP_INT.
            if (structDesc.eightByteSizes[slotNum] <= 4)
            {
                eightByteType = TYP_INT;
            }
            else if (structDesc.eightByteSizes[slotNum] <= 8)
            {
                eightByteType = TYP_LONG;
            }
            else
            {
                assert(false && "GetEightByteType Invalid Integer classification type.");
            }
            break;
        case SystemVClassificationTypeIntegerReference:
            assert(len == REGSIZE_BYTES);
            eightByteType = TYP_REF;
            break;
        case SystemVClassificationTypeIntegerByRef:
            assert(len == REGSIZE_BYTES);
            eightByteType = TYP_BYREF;
            break;
        case SystemVClassificationTypeSSE:
            if (structDesc.eightByteSizes[slotNum] <= 4)
            {
                eightByteType = TYP_FLOAT;
            }
            else if (structDesc.eightByteSizes[slotNum] <= 8)
            {
                eightByteType = TYP_DOUBLE;
            }
            else
            {
                assert(false && "GetEightByteType Invalid SSE classification type.");
            }
            break;
        default:
            assert(false && "GetEightByteType Invalid classification type.");
            break;
    }

    return eightByteType;
}

//------------------------------------------------------------------------------------------------------
// GetStructTypeOffset: Gets the type, size and offset of the eightbytes of a struct for System V systems.
//
// Arguments:
//    'structDesc' -  struct description
//    'type0'      -  out param; returns the type of the first eightbyte.
//    'type1'      -  out param; returns the type of the second eightbyte.
//    'offset0'    -  out param; returns the offset of the first eightbyte.
//    'offset1'    -  out param; returns the offset of the second eightbyte.
//
// static
void Compiler::GetStructTypeOffset(const SYSTEMV_AMD64_CORINFO_STRUCT_REG_PASSING_DESCRIPTOR& structDesc,
                                   var_types*                                                 type0,
                                   var_types*                                                 type1,
                                   uint8_t*                                                   offset0,
                                   uint8_t*                                                   offset1)
{
    *offset0 = structDesc.eightByteOffsets[0];
    *offset1 = structDesc.eightByteOffsets[1];

    *type0 = TYP_UNKNOWN;
    *type1 = TYP_UNKNOWN;

    // Set the first eightbyte data
    if (structDesc.eightByteCount >= 1)
    {
        *type0 = GetEightByteType(structDesc, 0);
    }

    // Set the second eight byte data
    if (structDesc.eightByteCount == 2)
    {
        *type1 = GetEightByteType(structDesc, 1);
    }
}

//------------------------------------------------------------------------------------------------------
// GetStructTypeOffset: Gets the type, size and offset of the eightbytes of a struct for System V systems.
//
// Arguments:
//    'typeHnd'    -  type handle
//    'type0'      -  out param; returns the type of the first eightbyte.
//    'type1'      -  out param; returns the type of the second eightbyte.
//    'offset0'    -  out param; returns the offset of the first eightbyte.
//    'offset1'    -  out param; returns the offset of the second eightbyte.
//
void Compiler::GetStructTypeOffset(
    CORINFO_CLASS_HANDLE typeHnd, var_types* type0, var_types* type1, uint8_t* offset0, uint8_t* offset1)
{
    SYSTEMV_AMD64_CORINFO_STRUCT_REG_PASSING_DESCRIPTOR structDesc;
    eeGetSystemVAmd64PassStructInRegisterDescriptor(typeHnd, &structDesc);
    assert(structDesc.passedInRegisters);
    GetStructTypeOffset(structDesc, type0, type1, offset0, offset1);
}

#elif defined(TARGET_RISCV64) || defined(TARGET_LOONGARCH64)
//------------------------------------------------------------------------
// GetFpStructLowering: Gets the information on passing of a struct according to hardware floating-point
// calling convention, i.e. the types and offsets of struct fields lowered for passing.
//
// Arguments:
//      structHandle - type handle
//
// Return value:
//      Lowering info for the struct fields
const CORINFO_FPSTRUCT_LOWERING* Compiler::GetFpStructLowering(CORINFO_CLASS_HANDLE structHandle)
{
    if (m_fpStructLoweringCache == nullptr)
        m_fpStructLoweringCache = new (this, CMK_CallArgs) FpStructLoweringMap(getAllocator(CMK_CallArgs));

    CORINFO_FPSTRUCT_LOWERING* lowering;
    if (!m_fpStructLoweringCache->Lookup(structHandle, &lowering))
    {
        lowering = new (this, CMK_CallArgs) CORINFO_FPSTRUCT_LOWERING;
        info.compCompHnd->getFpStructLowering(structHandle, lowering);
        m_fpStructLoweringCache->Set(structHandle, lowering);
#ifdef DEBUG
        if (verbose)
        {
            printf("**** getFpStructInRegistersInfo(0x%x (%s, %u bytes)) =>\n", dspPtr(structHandle),
                   eeGetClassName(structHandle), info.compCompHnd->getClassSize(structHandle));

            if (lowering->byIntegerCallConv)
            {
                printf("        pass by integer calling convention\n");
            }
            else
            {
                printf("        may be passed by floating-point calling convention (%zu fields):\n",
                       lowering->numLoweredElements);
                for (size_t i = 0; i < lowering->numLoweredElements; ++i)
                {
                    const char* type = varTypeName(JITtype2varType(lowering->loweredElements[i]));
                    printf("         * field[%zu]: type %s at offset %u\n", i, type, lowering->offsets[i]);
                }
            }
        }
#endif // DEBUG
    }
    return lowering;
}

#endif // defined(UNIX_AMD64_ABI)

/*****************************************************************************/
/*****************************************************************************/

#ifdef DEBUG
Compiler::NodeToIntMap* Compiler::FindReachableNodesInNodeTestData()
{
    NodeToIntMap* reachable = new (getAllocatorDebugOnly()) NodeToIntMap(getAllocatorDebugOnly());

    if (m_nodeTestData == nullptr)
    {
        return reachable;
    }

    // Otherwise, iterate.

    for (BasicBlock* const block : Blocks())
    {
        for (Statement* const stmt : block->NonPhiStatements())
        {
            for (GenTree* const tree : stmt->TreeList())
            {
                TestLabelAndNum tlAndN;

                // For call nodes, translate late args to what they stand for.
                if (tree->OperIs(GT_CALL))
                {
                    GenTreeCall* call = tree->AsCall();
                    unsigned     i    = 0;
                    for (CallArg& arg : call->gtArgs.Args())
                    {
                        GenTree* argNode = arg.GetNode();
                        if (GetNodeTestData()->Lookup(argNode, &tlAndN))
                        {
                            reachable->Set(argNode, 0);
                        }
                        i++;
                    }
                }

                if (GetNodeTestData()->Lookup(tree, &tlAndN))
                {
                    reachable->Set(tree, 0);
                }
            }
        }
    }
    return reachable;
}

void Compiler::TransferTestDataToNode(GenTree* from, GenTree* to)
{
    TestLabelAndNum tlAndN;
    // We can't currently associate multiple annotations with a single node.
    // If we need to, we can fix this...

    // If the table is null, don't create it just to do the lookup, which would fail...
    if (m_nodeTestData != nullptr && GetNodeTestData()->Lookup(from, &tlAndN))
    {
        assert(!GetNodeTestData()->Lookup(to, &tlAndN));
        // We can't currently associate multiple annotations with a single node.
        // If we need to, we can fix this...
        TestLabelAndNum tlAndNTo;
        assert(!GetNodeTestData()->Lookup(to, &tlAndNTo));

        GetNodeTestData()->Remove(from);
        GetNodeTestData()->Set(to, tlAndN);
    }
}

#endif // DEBUG

/*
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
XX                                                                           XX
XX                          jvc                                              XX
XX                                                                           XX
XX  Functions for the stand-alone version of the JIT .                       XX
XX                                                                           XX
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
*/

/*****************************************************************************/
void codeGeneratorCodeSizeBeg()
{
}

/*****************************************************************************
 *
 *  Used for counting pointer assignments.
 */

/*****************************************************************************/
void codeGeneratorCodeSizeEnd()
{
}
/*****************************************************************************
 *
 *  Gather statistics - mainly used for the standalone
 *  Enable various #ifdef's to get the information you need
 */

void Compiler::compJitStats()
{
#if CALL_ARG_STATS

    /* Method types and argument statistics */
    compCallArgStats();
#endif // CALL_ARG_STATS
}

#if CALL_ARG_STATS

/*****************************************************************************
 *
 *  Gather statistics about method calls and arguments
 */

void Compiler::compCallArgStats()
{
    unsigned argNum;

    unsigned argDWordNum;
    unsigned argLngNum;
    unsigned argFltNum;
    unsigned argDblNum;

    unsigned regArgNum;
    unsigned regArgDeferred;
    unsigned regArgTemp;

    unsigned regArgLclVar;
    unsigned regArgConst;

    unsigned argTempsThisMethod = 0;

    assert(fgStmtListThreaded);

    for (BasicBlock* const block : Blocks())
    {
        for (Statement* const stmt : block->Statements())
        {
            for (GenTree* const call : stmt->TreeList())
            {
                if (!call->OperIs(GT_CALL))
                    continue;

                argNum = regArgNum = regArgDeferred = regArgTemp = regArgConst = regArgLclVar = argDWordNum =
                    argLngNum = argFltNum = argDblNum = 0;

                argTotalCalls++;

                if (call->AsCall()->gtCallThisArg == nullptr)
                {
                    if (call->AsCall()->IsHelperCall())
                    {
                        argHelperCalls++;
                    }
                    else
                    {
                        argStaticCalls++;
                    }
                }
                else
                {
                    /* We have a 'this' pointer */

                    argDWordNum++;
                    argNum++;
                    regArgNum++;
                    regArgDeferred++;
                    argTotalObjPtr++;

                    if (call->AsCall()->IsVirtual())
                    {
                        /* virtual function */
                        argVirtualCalls++;
                    }
                    else
                    {
                        argNonVirtualCalls++;
                    }
                }
            }
        }
    }

    argTempsCntTable.record(argTempsThisMethod);

    if (argMaxTempsPerMethod < argTempsThisMethod)
    {
        argMaxTempsPerMethod = argTempsThisMethod;
    }
}

/* static */
void Compiler::compDispCallArgStats(FILE* fout)
{
    if (argTotalCalls == 0)
        return;

    fprintf(fout, "\n");
    fprintf(fout, "--------------------------------------------------\n");
    fprintf(fout, "Call stats\n");
    fprintf(fout, "--------------------------------------------------\n");
    fprintf(fout, "Total # of calls = %d, calls / method = %.3f\n\n", argTotalCalls,
            (float)argTotalCalls / genMethodCnt);

    fprintf(fout, "Percentage of      helper calls = %4.2f %%\n", (float)(100 * argHelperCalls) / argTotalCalls);
    fprintf(fout, "Percentage of      static calls = %4.2f %%\n", (float)(100 * argStaticCalls) / argTotalCalls);
    fprintf(fout, "Percentage of     virtual calls = %4.2f %%\n", (float)(100 * argVirtualCalls) / argTotalCalls);
    fprintf(fout, "Percentage of non-virtual calls = %4.2f %%\n\n", (float)(100 * argNonVirtualCalls) / argTotalCalls);

    fprintf(fout, "Average # of arguments per call = %.2f%%\n\n", (float)argTotalArgs / argTotalCalls);

    fprintf(fout, "Percentage of DWORD  arguments   = %.2f %%\n", (float)(100 * argTotalDWordArgs) / argTotalArgs);
    fprintf(fout, "Percentage of LONG   arguments   = %.2f %%\n", (float)(100 * argTotalLongArgs) / argTotalArgs);
    fprintf(fout, "Percentage of FLOAT  arguments   = %.2f %%\n", (float)(100 * argTotalFloatArgs) / argTotalArgs);
    fprintf(fout, "Percentage of DOUBLE arguments   = %.2f %%\n\n", (float)(100 * argTotalDoubleArgs) / argTotalArgs);

    if (argTotalRegArgs == 0)
        return;

    /*
        fprintf(fout, "Total deferred arguments     = %d \n", argTotalDeferred);

        fprintf(fout, "Total temp arguments         = %d \n\n", argTotalTemps);

        fprintf(fout, "Total 'this' arguments       = %d \n", argTotalObjPtr);
        fprintf(fout, "Total local var arguments    = %d \n", argTotalLclVar);
        fprintf(fout, "Total constant arguments     = %d \n\n", argTotalConst);
    */

    fprintf(fout, "\nRegister Arguments:\n\n");

    fprintf(fout, "Percentage of deferred arguments = %.2f %%\n", (float)(100 * argTotalDeferred) / argTotalRegArgs);
    fprintf(fout, "Percentage of temp arguments     = %.2f %%\n\n", (float)(100 * argTotalTemps) / argTotalRegArgs);

    fprintf(fout, "Maximum # of temps per method    = %d\n\n", argMaxTempsPerMethod);

    fprintf(fout, "Percentage of ObjPtr arguments   = %.2f %%\n", (float)(100 * argTotalObjPtr) / argTotalRegArgs);
    // fprintf(fout, "Percentage of global arguments   = %.2f %%\n", (float)(100 * argTotalDWordGlobEf) /
    // argTotalRegArgs);
    fprintf(fout, "Percentage of constant arguments = %.2f %%\n", (float)(100 * argTotalConst) / argTotalRegArgs);
    fprintf(fout, "Percentage of lcl var arguments  = %.2f %%\n\n", (float)(100 * argTotalLclVar) / argTotalRegArgs);

    fprintf(fout, "--------------------------------------------------\n");
    fprintf(fout, "Argument count frequency table (includes ObjPtr):\n");
    fprintf(fout, "--------------------------------------------------\n");
    argCntTable.dump(fout);
    fprintf(fout, "--------------------------------------------------\n");

    fprintf(fout, "--------------------------------------------------\n");
    fprintf(fout, "DWORD argument count frequency table (w/o LONG):\n");
    fprintf(fout, "--------------------------------------------------\n");
    argDWordCntTable.dump(fout);
    fprintf(fout, "--------------------------------------------------\n");

    fprintf(fout, "--------------------------------------------------\n");
    fprintf(fout, "Temps count frequency table (per method):\n");
    fprintf(fout, "--------------------------------------------------\n");
    argTempsCntTable.dump(fout);
    fprintf(fout, "--------------------------------------------------\n");

    /*
        fprintf(fout, "--------------------------------------------------\n");
        fprintf(fout, "DWORD argument count frequency table (w/ LONG):\n");
        fprintf(fout, "--------------------------------------------------\n");
        argDWordLngCntTable.dump(fout);
        fprintf(fout, "--------------------------------------------------\n");
    */
}

#endif // CALL_ARG_STATS

// JIT time end to end, and by phases.

#ifdef FEATURE_JIT_METHOD_PERF
// Static variables
CritSecObject       CompTimeSummaryInfo::s_compTimeSummaryLock;
CompTimeSummaryInfo CompTimeSummaryInfo::s_compTimeSummary;
#if MEASURE_CLRAPI_CALLS
double JitTimer::s_cyclesPerSec = CachedCyclesPerSecond();
#endif
#endif // FEATURE_JIT_METHOD_PERF

#if defined(FEATURE_JIT_METHOD_PERF) || DUMP_FLOWGRAPHS
const char* PhaseNames[] = {
#define CompPhaseNameMacro(enum_nm, string_nm, hasChildren, parent, measureIR) string_nm,
#include "compphases.h"
};

const char* PhaseEnums[] = {
#define CompPhaseNameMacro(enum_nm, string_nm, hasChildren, parent, measureIR) #enum_nm,
#include "compphases.h"
};

#endif // defined(FEATURE_JIT_METHOD_PERF) || DUMP_FLOWGRAPHS

#ifdef FEATURE_JIT_METHOD_PERF
bool PhaseHasChildren[] = {
#define CompPhaseNameMacro(enum_nm, string_nm, hasChildren, parent, measureIR) hasChildren,
#include "compphases.h"
};

int PhaseParent[] = {
#define CompPhaseNameMacro(enum_nm, string_nm, hasChildren, parent, measureIR) parent,
#include "compphases.h"
};

bool PhaseReportsIRSize[] = {
#define CompPhaseNameMacro(enum_nm, string_nm, hasChildren, parent, measureIR) measureIR,
#include "compphases.h"
};

CompTimeInfo::CompTimeInfo(unsigned byteCodeBytes)
    : m_byteCodeBytes(byteCodeBytes)
    , m_totalCycles(0)
    , m_parentPhaseEndSlop(0)
    , m_timerFailure(false)
#if MEASURE_CLRAPI_CALLS
    , m_allClrAPIcalls(0)
    , m_allClrAPIcycles(0)
#endif
{
    for (int i = 0; i < PHASE_NUMBER_OF; i++)
    {
        m_invokesByPhase[i] = 0;
        m_cyclesByPhase[i]  = 0;
#if MEASURE_CLRAPI_CALLS
        m_CLRinvokesByPhase[i] = 0;
        m_CLRcyclesByPhase[i]  = 0;
#endif
    }

#if MEASURE_CLRAPI_CALLS
    assert(ArrLen(m_perClrAPIcalls) == API_ICorJitInfo_Names::API_COUNT);
    assert(ArrLen(m_perClrAPIcycles) == API_ICorJitInfo_Names::API_COUNT);
    assert(ArrLen(m_maxClrAPIcycles) == API_ICorJitInfo_Names::API_COUNT);
    for (int i = 0; i < API_ICorJitInfo_Names::API_COUNT; i++)
    {
        m_perClrAPIcalls[i]  = 0;
        m_perClrAPIcycles[i] = 0;
        m_maxClrAPIcycles[i] = 0;
    }
#endif
}

bool CompTimeSummaryInfo::IncludedInFilteredData(CompTimeInfo& info)
{
    return false; // info.m_byteCodeBytes < 10;
}

//------------------------------------------------------------------------
// CompTimeSummaryInfo::AddInfo: Record timing info from one compile.
//
// Arguments:
//    info          - The timing information to record.
//    includePhases - If "true", the per-phase info in "info" is valid,
//                    which means that a "normal" compile has ended; if
//                    the value is "false" we are recording the results
//                    of a partial compile (typically an import-only run
//                    on behalf of the inliner) in which case the phase
//                    info is not valid and so we only record EE call
//                    overhead.
void CompTimeSummaryInfo::AddInfo(CompTimeInfo& info, bool includePhases)
{
    if (info.m_timerFailure)
    {
        return; // Don't update if there was a failure.
    }

    CritSecHolder timeLock(s_compTimeSummaryLock);

    if (includePhases)
    {
        bool includeInFiltered = IncludedInFilteredData(info);

        m_numMethods++;

        // Update the totals and maxima.
        m_total.m_byteCodeBytes += info.m_byteCodeBytes;
        m_maximum.m_byteCodeBytes = max(m_maximum.m_byteCodeBytes, info.m_byteCodeBytes);
        m_total.m_totalCycles += info.m_totalCycles;
        m_maximum.m_totalCycles = max(m_maximum.m_totalCycles, info.m_totalCycles);

#if MEASURE_CLRAPI_CALLS
        // Update the CLR-API values.
        m_total.m_allClrAPIcalls += info.m_allClrAPIcalls;
        m_maximum.m_allClrAPIcalls = max(m_maximum.m_allClrAPIcalls, info.m_allClrAPIcalls);
        m_total.m_allClrAPIcycles += info.m_allClrAPIcycles;
        m_maximum.m_allClrAPIcycles = max(m_maximum.m_allClrAPIcycles, info.m_allClrAPIcycles);
#endif

        if (includeInFiltered)
        {
            m_numFilteredMethods++;
            m_filtered.m_byteCodeBytes += info.m_byteCodeBytes;
            m_filtered.m_totalCycles += info.m_totalCycles;
            m_filtered.m_parentPhaseEndSlop += info.m_parentPhaseEndSlop;
        }

        for (int i = 0; i < PHASE_NUMBER_OF; i++)
        {
            m_total.m_invokesByPhase[i] += info.m_invokesByPhase[i];
            m_total.m_cyclesByPhase[i] += info.m_cyclesByPhase[i];

#if MEASURE_CLRAPI_CALLS
            m_total.m_CLRinvokesByPhase[i] += info.m_CLRinvokesByPhase[i];
            m_total.m_CLRcyclesByPhase[i] += info.m_CLRcyclesByPhase[i];
#endif

            if (includeInFiltered)
            {
                m_filtered.m_invokesByPhase[i] += info.m_invokesByPhase[i];
                m_filtered.m_cyclesByPhase[i] += info.m_cyclesByPhase[i];
#if MEASURE_CLRAPI_CALLS
                m_filtered.m_CLRinvokesByPhase[i] += info.m_CLRinvokesByPhase[i];
                m_filtered.m_CLRcyclesByPhase[i] += info.m_CLRcyclesByPhase[i];
#endif
            }
            m_maximum.m_cyclesByPhase[i] = max(m_maximum.m_cyclesByPhase[i], info.m_cyclesByPhase[i]);

#if MEASURE_CLRAPI_CALLS
            m_maximum.m_CLRcyclesByPhase[i] = max(m_maximum.m_CLRcyclesByPhase[i], info.m_CLRcyclesByPhase[i]);
#endif
        }
        m_total.m_parentPhaseEndSlop += info.m_parentPhaseEndSlop;
        m_maximum.m_parentPhaseEndSlop = max(m_maximum.m_parentPhaseEndSlop, info.m_parentPhaseEndSlop);
    }
#if MEASURE_CLRAPI_CALLS
    else
    {
        m_totMethods++;

        // Update the "global" CLR-API values.
        m_total.m_allClrAPIcalls += info.m_allClrAPIcalls;
        m_maximum.m_allClrAPIcalls = max(m_maximum.m_allClrAPIcalls, info.m_allClrAPIcalls);
        m_total.m_allClrAPIcycles += info.m_allClrAPIcycles;
        m_maximum.m_allClrAPIcycles = max(m_maximum.m_allClrAPIcycles, info.m_allClrAPIcycles);

        // Update the per-phase CLR-API values.
        m_total.m_invokesByPhase[PHASE_CLR_API] += info.m_allClrAPIcalls;
        m_maximum.m_invokesByPhase[PHASE_CLR_API] =
            max(m_maximum.m_perClrAPIcalls[PHASE_CLR_API], info.m_allClrAPIcalls);
        m_total.m_cyclesByPhase[PHASE_CLR_API] += info.m_allClrAPIcycles;
        m_maximum.m_cyclesByPhase[PHASE_CLR_API] =
            max(m_maximum.m_cyclesByPhase[PHASE_CLR_API], info.m_allClrAPIcycles);
    }

    for (int i = 0; i < API_ICorJitInfo_Names::API_COUNT; i++)
    {
        m_total.m_perClrAPIcalls[i] += info.m_perClrAPIcalls[i];
        m_maximum.m_perClrAPIcalls[i] = max(m_maximum.m_perClrAPIcalls[i], info.m_perClrAPIcalls[i]);

        m_total.m_perClrAPIcycles[i] += info.m_perClrAPIcycles[i];
        m_maximum.m_perClrAPIcycles[i] = max(m_maximum.m_perClrAPIcycles[i], info.m_perClrAPIcycles[i]);

        m_maximum.m_maxClrAPIcycles[i] = max(m_maximum.m_maxClrAPIcycles[i], info.m_maxClrAPIcycles[i]);
    }
#endif
}

// Static
const char* Compiler::compJitTimeLogFilename = nullptr;

void CompTimeSummaryInfo::Print(FILE* f)
{
    if (f == nullptr)
    {
        return;
    }
    // Otherwise...
    double countsPerSec = CachedCyclesPerSecond();
    if (countsPerSec == 0.0)
    {
        fprintf(f, "Processor does not have a high-frequency timer.\n");
        return;
    }

    double totTime_ms = 0.0;

    fprintf(f, "JIT Compilation time report:\n");
    fprintf(f, "  Compiled %d methods.\n", m_numMethods);
    if (m_numMethods != 0)
    {
        fprintf(f, "  Compiled %d bytecodes total (%d max, %8.2f avg).\n", m_total.m_byteCodeBytes,
                m_maximum.m_byteCodeBytes, (double)m_total.m_byteCodeBytes / (double)m_numMethods);
        totTime_ms = ((double)m_total.m_totalCycles / countsPerSec) * 1000.0;
        fprintf(f, "  Time: total: %10.3f Mcycles/%10.3f ms\n", ((double)m_total.m_totalCycles / 1000000.0),
                totTime_ms);
        fprintf(f, "          max: %10.3f Mcycles/%10.3f ms\n", ((double)m_maximum.m_totalCycles) / 1000000.0,
                ((double)m_maximum.m_totalCycles / countsPerSec) * 1000.0);
        fprintf(f, "          avg: %10.3f Mcycles/%10.3f ms\n",
                ((double)m_total.m_totalCycles) / 1000000.0 / (double)m_numMethods, totTime_ms / (double)m_numMethods);

        const char* extraHdr1 = "";
        const char* extraHdr2 = "";
#if MEASURE_CLRAPI_CALLS
        bool extraInfo = (JitConfig.JitEECallTimingInfo() != 0);
        if (extraInfo)
        {
            extraHdr1 = "    CLRs/meth   % in CLR";
            extraHdr2 = "-----------------------";
        }
#endif

        fprintf(f, "\n  Total time by phases:\n");
        fprintf(f, "     PHASE                          inv/meth   Mcycles    time (ms)  %% of total    max (ms)%s\n",
                extraHdr1);
        fprintf(f, "     ---------------------------------------------------------------------------------------%s\n",
                extraHdr2);

        // Ensure that at least the names array and the Phases enum have the same number of entries:
        assert(ArrLen(PhaseNames) == PHASE_NUMBER_OF);
        for (int i = 0; i < PHASE_NUMBER_OF; i++)
        {
            double phase_tot_ms = (((double)m_total.m_cyclesByPhase[i]) / countsPerSec) * 1000.0;
            double phase_max_ms = (((double)m_maximum.m_cyclesByPhase[i]) / countsPerSec) * 1000.0;

#if MEASURE_CLRAPI_CALLS
            // Skip showing CLR API call info if we didn't collect any
            if (i == PHASE_CLR_API && !extraInfo)
                continue;
#endif

            // Indent nested phases, according to depth.
            int ancPhase = PhaseParent[i];
            while (ancPhase != -1)
            {
                fprintf(f, "  ");
                ancPhase = PhaseParent[ancPhase];
            }
            fprintf(f, "     %-30s %6.2f  %10.2f   %9.3f   %8.2f%%    %8.3f", PhaseNames[i],
                    ((double)m_total.m_invokesByPhase[i]) / ((double)m_numMethods),
                    ((double)m_total.m_cyclesByPhase[i]) / 1000000.0, phase_tot_ms, (phase_tot_ms * 100.0 / totTime_ms),
                    phase_max_ms);

#if MEASURE_CLRAPI_CALLS
            if (extraInfo && i != PHASE_CLR_API)
            {
                double nest_tot_ms  = (((double)m_total.m_CLRcyclesByPhase[i]) / countsPerSec) * 1000.0;
                double nest_percent = nest_tot_ms * 100.0 / totTime_ms;
                double calls_per_fn = ((double)m_total.m_CLRinvokesByPhase[i]) / ((double)m_numMethods);

                if (nest_percent > 0.1 || calls_per_fn > 10)
                    fprintf(f, "       %5.1f   %8.2f%%", calls_per_fn, nest_percent);
            }
#endif
            fprintf(f, "\n");
        }

        // Show slop if it's over a certain percentage of the total
        double pslop_pct = 100.0 * m_total.m_parentPhaseEndSlop * 1000.0 / countsPerSec / totTime_ms;
        if (pslop_pct >= 1.0)
        {
            fprintf(f,
                    "\n  'End phase slop' should be very small (if not, there's unattributed time): %9.3f Mcycles = "
                    "%3.1f%% of total.\n\n",
                    m_total.m_parentPhaseEndSlop / 1000000.0, pslop_pct);
        }
    }
    if (m_numFilteredMethods > 0)
    {
        fprintf(f, "  Compiled %d methods that meet the filter requirement.\n", m_numFilteredMethods);
        fprintf(f, "  Compiled %d bytecodes total (%8.2f avg).\n", m_filtered.m_byteCodeBytes,
                (double)m_filtered.m_byteCodeBytes / (double)m_numFilteredMethods);
        double totTime_ms = ((double)m_filtered.m_totalCycles / countsPerSec) * 1000.0;
        fprintf(f, "  Time: total: %10.3f Mcycles/%10.3f ms\n", ((double)m_filtered.m_totalCycles / 1000000.0),
                totTime_ms);
        fprintf(f, "          avg: %10.3f Mcycles/%10.3f ms\n",
                ((double)m_filtered.m_totalCycles) / 1000000.0 / (double)m_numFilteredMethods,
                totTime_ms / (double)m_numFilteredMethods);

        fprintf(f, "  Total time by phases:\n");
        fprintf(f, "     PHASE                            inv/meth Mcycles    time (ms)  %% of total\n");
        fprintf(f, "     --------------------------------------------------------------------------------------\n");
        // Ensure that at least the names array and the Phases enum have the same number of entries:
        assert(ArrLen(PhaseNames) == PHASE_NUMBER_OF);
        for (int i = 0; i < PHASE_NUMBER_OF; i++)
        {
            double phase_tot_ms = (((double)m_filtered.m_cyclesByPhase[i]) / countsPerSec) * 1000.0;
            // Indent nested phases, according to depth.
            int ancPhase = PhaseParent[i];
            while (ancPhase != -1)
            {
                fprintf(f, "  ");
                ancPhase = PhaseParent[ancPhase];
            }
            fprintf(f, "     %-30s  %5.2f  %10.2f   %9.3f   %8.2f%%\n", PhaseNames[i],
                    ((double)m_filtered.m_invokesByPhase[i]) / ((double)m_numFilteredMethods),
                    ((double)m_filtered.m_cyclesByPhase[i]) / 1000000.0, phase_tot_ms,
                    (phase_tot_ms * 100.0 / totTime_ms));
        }

        double fslop_ms = m_filtered.m_parentPhaseEndSlop * 1000.0 / countsPerSec;
        if (fslop_ms > 1.0)
        {
            fprintf(f,
                    "\n  'End phase slop' should be very small (if not, there's unattributed time): %9.3f Mcycles = "
                    "%3.1f%% of total.\n\n",
                    m_filtered.m_parentPhaseEndSlop / 1000000.0, fslop_ms);
        }
    }

#if MEASURE_CLRAPI_CALLS
    if (m_total.m_allClrAPIcalls > 0 && m_total.m_allClrAPIcycles > 0)
    {
        fprintf(f, "\n");
        if (m_totMethods > 0)
            fprintf(f, "  Imported %u methods.\n\n", m_numMethods + m_totMethods);

        fprintf(f, "     CLR API                                   # calls   total time    max time     avg time   %% "
                   "of total\n");
        fprintf(f, "     -------------------------------------------------------------------------------");
        fprintf(f, "---------------------\n");

        static const char* APInames[] = {
#define DEF_CLR_API(name) #name,
#include "ICorJitInfo_names_generated.h"
        };

        unsigned shownCalls  = 0;
        double   shownMillis = 0.0;
#ifdef DEBUG
        unsigned checkedCalls  = 0;
        double   checkedMillis = 0.0;
#endif

        for (unsigned pass = 0; pass < 2; pass++)
        {
            for (unsigned i = 0; i < API_ICorJitInfo_Names::API_COUNT; i++)
            {
                unsigned calls = m_total.m_perClrAPIcalls[i];
                if (calls == 0)
                    continue;

                uint64_t cycles = m_total.m_perClrAPIcycles[i];
                double   millis = 1000.0 * cycles / countsPerSec;

                // Don't show the small fry to keep the results manageable
                if (millis < 0.5)
                {
                    // We always show the following API because it is always called
                    // exactly once for each method and its body is the simplest one
                    // possible (it just returns an integer constant), and therefore
                    // it can be used to measure the overhead of adding the CLR API
                    // timing code. Roughly speaking, on a 3GHz x64 box the overhead
                    // per call should be around 40 ns when using RDTSC, compared to
                    // about 140 ns when using GetThreadCycles() under Windows.
                    if (i != API_ICorJitInfo_Names::API_getExpectedTargetArchitecture)
                        continue;
                }

                // In the first pass we just compute the totals.
                if (pass == 0)
                {
                    shownCalls += m_total.m_perClrAPIcalls[i];
                    shownMillis += millis;
                    continue;
                }

                uint32_t maxcyc = m_maximum.m_maxClrAPIcycles[i];
                double   max_ms = 1000.0 * maxcyc / countsPerSec;

                fprintf(f, "     %-40s", APInames[i]);                                 // API name
                fprintf(f, " %8u %9.1f ms", calls, millis);                            // #calls, total time
                fprintf(f, " %8.1f ms  %8.1f ns", max_ms, 1000000.0 * millis / calls); // max, avg time
                fprintf(f, "     %5.1f%%\n", 100.0 * millis / shownMillis);            // % of total

#ifdef DEBUG
                checkedCalls += m_total.m_perClrAPIcalls[i];
                checkedMillis += millis;
#endif
            }
        }

#ifdef DEBUG
        assert(checkedCalls == shownCalls);
        assert(checkedMillis == shownMillis);
#endif

        if (shownCalls > 0 || shownMillis > 0)
        {
            fprintf(f, "     -------------------------");
            fprintf(f, "---------------------------------------------------------------------------\n");
            fprintf(f, "     Total for calls shown above              %8u %10.1f ms", shownCalls, shownMillis);
            if (totTime_ms > 0.0)
                fprintf(f, " (%4.1lf%% of overall JIT time)", shownMillis * 100.0 / totTime_ms);
            fprintf(f, "\n");
        }
        fprintf(f, "\n");
    }
#endif

    fprintf(f, "\n");
}

JitTimer::JitTimer(unsigned byteCodeSize)
    : m_info(byteCodeSize)
{
#if MEASURE_CLRAPI_CALLS
    m_CLRcallInvokes = 0;
    m_CLRcallCycles  = 0;
#endif

#ifdef DEBUG
    m_lastPhase = (Phases)-1;
#if MEASURE_CLRAPI_CALLS
    m_CLRcallAPInum = -1;
#endif
#endif

    uint64_t threadCurCycles;
    if (_our_GetThreadCycles(&threadCurCycles))
    {
        m_start         = threadCurCycles;
        m_curPhaseStart = threadCurCycles;
    }
}

void JitTimer::EndPhase(Compiler* compiler, Phases phase)
{
    // Otherwise...
    // We re-run some phases currently, so this following assert doesn't work.
    // assert((int)phase > (int)m_lastPhase);  // We should end phases in increasing order.

    uint64_t threadCurCycles;
    if (_our_GetThreadCycles(&threadCurCycles))
    {
        uint64_t phaseCycles = (threadCurCycles - m_curPhaseStart);

        // If this is not a leaf phase, the assumption is that the last subphase must have just recently ended.
        // Credit the duration to "slop", the total of which should be very small.
        if (PhaseHasChildren[phase])
        {
            m_info.m_parentPhaseEndSlop += phaseCycles;
        }
        else
        {
            // It is a leaf phase.  Credit duration to it.
            m_info.m_invokesByPhase[phase]++;
            m_info.m_cyclesByPhase[phase] += phaseCycles;

#if MEASURE_CLRAPI_CALLS
            // Record the CLR API timing info as well.
            m_info.m_CLRinvokesByPhase[phase] += m_CLRcallInvokes;
            m_info.m_CLRcyclesByPhase[phase] += m_CLRcallCycles;
#endif

            // Credit the phase's ancestors, if any.
            int ancPhase = PhaseParent[phase];
            while (ancPhase != -1)
            {
                m_info.m_cyclesByPhase[ancPhase] += phaseCycles;
                ancPhase = PhaseParent[ancPhase];
            }

#if MEASURE_CLRAPI_CALLS
            const Phases lastPhase = PHASE_CLR_API;
#else
            const Phases lastPhase = PHASE_NUMBER_OF;
#endif
            if (phase + 1 == lastPhase)
            {
                m_info.m_totalCycles = (threadCurCycles - m_start);
            }
            else
            {
                m_curPhaseStart = threadCurCycles;
            }
        }

        if ((JitConfig.JitMeasureIR() != 0) && PhaseReportsIRSize[phase])
        {
            m_info.m_nodeCountAfterPhase[phase] = compiler->fgMeasureIR();
        }
        else
        {
            m_info.m_nodeCountAfterPhase[phase] = 0;
        }
    }

#ifdef DEBUG
    m_lastPhase = phase;
#endif
#if MEASURE_CLRAPI_CALLS
    m_CLRcallInvokes = 0;
    m_CLRcallCycles  = 0;
#endif
}

#if MEASURE_CLRAPI_CALLS

//------------------------------------------------------------------------
// JitTimer::CLRApiCallEnter: Start the stopwatch for an EE call.
//
// Arguments:
//    apix - The API index - an "enum API_ICorJitInfo_Names" value.
//

void JitTimer::CLRApiCallEnter(unsigned apix)
{
    assert(m_CLRcallAPInum == -1); // Nested calls not allowed
    m_CLRcallAPInum = apix;

    // If we can't get the cycles, we'll just ignore this call
    if (!_our_GetThreadCycles(&m_CLRcallStart))
        m_CLRcallStart = 0;
}

//------------------------------------------------------------------------
// JitTimer::CLRApiCallLeave: compute / record time spent in an EE call.
//
// Arguments:
//    apix - The API's "enum API_ICorJitInfo_Names" value; this value
//           should match the value passed to the most recent call to
//           "CLRApiCallEnter" (i.e. these must come as matched pairs),
//           and they also may not nest.
//

void JitTimer::CLRApiCallLeave(unsigned apix)
{
    // Make sure we're actually inside a measured CLR call.
    assert(m_CLRcallAPInum != -1);
    m_CLRcallAPInum = -1;

    // Ignore this one if we don't have a valid starting counter.
    if (m_CLRcallStart != 0)
    {
        if (JitConfig.JitEECallTimingInfo() != 0)
        {
            uint64_t threadCurCycles;
            if (_our_GetThreadCycles(&threadCurCycles))
            {
                // Compute the cycles spent in the call.
                threadCurCycles -= m_CLRcallStart;

                // Add the cycles to the 'phase' and bump its use count.
                m_info.m_cyclesByPhase[PHASE_CLR_API] += threadCurCycles;
                m_info.m_invokesByPhase[PHASE_CLR_API] += 1;

                // Add the values to the "per API" info.
                m_info.m_allClrAPIcycles += threadCurCycles;
                m_info.m_allClrAPIcalls += 1;

                m_info.m_perClrAPIcalls[apix] += 1;
                m_info.m_perClrAPIcycles[apix] += threadCurCycles;
                m_info.m_maxClrAPIcycles[apix] = max(m_info.m_maxClrAPIcycles[apix], (uint32_t)threadCurCycles);

                // Subtract the cycles from the enclosing phase by bumping its start time
                m_curPhaseStart += threadCurCycles;

                // Update the running totals.
                m_CLRcallInvokes += 1;
                m_CLRcallCycles += threadCurCycles;
            }
        }

        m_CLRcallStart = 0;
    }

    assert(m_CLRcallAPInum != -1); // No longer in this API call.
    m_CLRcallAPInum = -1;
}

#endif // MEASURE_CLRAPI_CALLS

CritSecObject JitTimer::s_csvLock;

// It's expensive to constantly open and close the file, so open it once and close it
// when the process exits. This should be accessed under the s_csvLock.
FILE* JitTimer::s_csvFile = nullptr;

const char* Compiler::JitTimeLogCsv()
{
    const char* jitTimeLogCsv = JitConfig.JitTimeLogCsv();
    return jitTimeLogCsv;
}

void JitTimer::PrintCsvHeader()
{
    const char* jitTimeLogCsv = Compiler::JitTimeLogCsv();
    if (jitTimeLogCsv == nullptr)
    {
        return;
    }

    CritSecHolder csvLock(s_csvLock);

    if (s_csvFile == nullptr)
    {
        s_csvFile = fopen_utf8(jitTimeLogCsv, "a");
    }
    if (s_csvFile != nullptr)
    {
        // Seek to the end of the file s.t. `ftell` doesn't lie to us on Windows
        fseek(s_csvFile, 0, SEEK_END);

        // Write the header if the file is empty
        if (ftell(s_csvFile) == 0)
        {
            fprintf(s_csvFile, "\"Method Name\",");
            fprintf(s_csvFile, "\"Assembly or SPMI Index\",");
            fprintf(s_csvFile, "\"IL Bytes\",");
            fprintf(s_csvFile, "\"Basic Blocks\",");
            fprintf(s_csvFile, "\"Min Opts\",");
            fprintf(s_csvFile, "\"Loops\",");
            fprintf(s_csvFile, "\"Loops Cloned\",");
#if FEATURE_LOOP_ALIGN
#ifdef DEBUG
            fprintf(s_csvFile, "\"Alignment Candidates\",");
            fprintf(s_csvFile, "\"Loops Aligned\",");
#endif // DEBUG
#endif // FEATURE_LOOP_ALIGN
            for (int i = 0; i < PHASE_NUMBER_OF; i++)
            {
                fprintf(s_csvFile, "\"%s\",", PhaseNames[i]);
                if ((JitConfig.JitMeasureIR() != 0) && PhaseReportsIRSize[i])
                {
                    fprintf(s_csvFile, "\"Node Count After %s\",", PhaseNames[i]);
                }
            }

            InlineStrategy::DumpCsvHeader(s_csvFile);

            fprintf(s_csvFile, "\"Executable Code Bytes\",");
            fprintf(s_csvFile, "\"GC Info Bytes\",");
            fprintf(s_csvFile, "\"Total Bytes Allocated\",");
            fprintf(s_csvFile, "\"Total Cycles\",");
            fprintf(s_csvFile, "\"CPS\"\n");

            fflush(s_csvFile);
        }
    }
}

void JitTimer::PrintCsvMethodStats(Compiler* comp)
{
    const char* jitTimeLogCsv = Compiler::JitTimeLogCsv();
    if (jitTimeLogCsv == nullptr)
    {
        return;
    }

// eeGetMethodFullName uses locks, so don't enter crit sec before this call.
#if defined(DEBUG) || defined(LATE_DISASM)
    // If we already have computed the name because for some reason we're generating the CSV
    // for a DEBUG build (presumably not for the time info), just re-use it.
    const char* methName = comp->info.compFullName;
#else
    const char* methName = comp->eeGetMethodFullName(comp->info.compMethodHnd);
#endif

    // Try and access the SPMI index to report in the data set.
    //
    // If the jit is not hosted under SPMI this will return the
    // default value of zero.
    //
    // Query the jit host directly here instead of going via the
    // config cache, since value will change for each method.
    int index = g_jitHost->getIntConfigValue("SuperPMIMethodContextNumber", -1);

    CritSecHolder csvLock(s_csvLock);

    if (s_csvFile == nullptr)
    {
        return;
    }

    fprintf(s_csvFile, "\"%s\",", methName);
    if (index != 0)
    {
        fprintf(s_csvFile, "%d,", index);
    }
    else
    {
        const char* methodAssemblyName = comp->eeGetClassAssemblyName(comp->info.compClassHnd);
        fprintf(s_csvFile, "\"%s\",", methodAssemblyName);
    }
    fprintf(s_csvFile, "%u,", comp->info.compILCodeSize);
    fprintf(s_csvFile, "%u,", comp->fgBBcount);
    fprintf(s_csvFile, "%u,", comp->opts.MinOpts());
    fprintf(s_csvFile, "%d,", comp->Metrics.LoopsFoundDuringOpts);
    fprintf(s_csvFile, "%d,", comp->Metrics.LoopsCloned);
#if FEATURE_LOOP_ALIGN
#ifdef DEBUG
    fprintf(s_csvFile, "%d,", comp->Metrics.LoopAlignmentCandidates);
    fprintf(s_csvFile, "%d,", comp->Metrics.LoopsAligned);
#endif // DEBUG
#endif // FEATURE_LOOP_ALIGN
    uint64_t totCycles = 0;
    for (int i = 0; i < PHASE_NUMBER_OF; i++)
    {
        if (!PhaseHasChildren[i])
        {
            totCycles += m_info.m_cyclesByPhase[i];
        }
        fprintf(s_csvFile, "%llu,", (unsigned long long)m_info.m_cyclesByPhase[i]);

        if ((JitConfig.JitMeasureIR() != 0) && PhaseReportsIRSize[i])
        {
            fprintf(s_csvFile, "%u,", m_info.m_nodeCountAfterPhase[i]);
        }
    }

    comp->m_inlineStrategy->DumpCsvData(s_csvFile);

    fprintf(s_csvFile, "%u,", comp->info.compNativeCodeSize);
    fprintf(s_csvFile, "%zu,", comp->compInfoBlkSize);
    fprintf(s_csvFile, "%zu,", comp->compGetArenaAllocator()->getTotalBytesAllocated());
    fprintf(s_csvFile, "%llu,", (unsigned long long)m_info.m_totalCycles);
    fprintf(s_csvFile, "%f\n", CachedCyclesPerSecond());

    fflush(s_csvFile);
}

// Perform process shutdown actions.
//
// static
void JitTimer::Shutdown()
{
    CritSecHolder csvLock(s_csvLock);
    if (s_csvFile != nullptr)
    {
        fclose(s_csvFile);
    }
}

// Completes the timing of the current method, and adds it to "sum".
void JitTimer::Terminate(Compiler* comp, CompTimeSummaryInfo& sum, bool includePhases)
{
    if (includePhases)
    {
        PrintCsvMethodStats(comp);
    }

    sum.AddInfo(m_info, includePhases);
}
#endif // FEATURE_JIT_METHOD_PERF

#if LOOP_HOIST_STATS
// Static fields.
CritSecObject Compiler::s_loopHoistStatsLock; // Default constructor.
unsigned      Compiler::s_loopsConsidered             = 0;
unsigned      Compiler::s_loopsWithHoistedExpressions = 0;
unsigned      Compiler::s_totalHoistedExpressions     = 0;

// static
void Compiler::PrintAggregateLoopHoistStats(FILE* f)
{
    fprintf(f, "\n");
    fprintf(f, "---------------------------------------------------\n");
    fprintf(f, "Loop hoisting stats\n");
    fprintf(f, "---------------------------------------------------\n");

    double pctWithHoisted = 0.0;
    if (s_loopsConsidered > 0)
    {
        pctWithHoisted = 100.0 * (double(s_loopsWithHoistedExpressions) / double(s_loopsConsidered));
    }
    double exprsPerLoopWithExpr = 0.0;
    if (s_loopsWithHoistedExpressions > 0)
    {
        exprsPerLoopWithExpr = double(s_totalHoistedExpressions) / double(s_loopsWithHoistedExpressions);
    }
    fprintf(f, "Considered %d loops.  Of these, we hoisted expressions out of %d (%6.2f%%).\n", s_loopsConsidered,
            s_loopsWithHoistedExpressions, pctWithHoisted);
    fprintf(f, "  A total of %d expressions were hoisted, an average of %5.2f per loop-with-hoisted-expr.\n",
            s_totalHoistedExpressions, exprsPerLoopWithExpr);
}

void Compiler::AddLoopHoistStats()
{
    CritSecHolder statsLock(s_loopHoistStatsLock);

    s_loopsConsidered += m_loopsConsidered;
    s_loopsWithHoistedExpressions += m_loopsWithHoistedExpressions;
    s_totalHoistedExpressions += m_totalHoistedExpressions;
}

void Compiler::PrintPerMethodLoopHoistStats()
{
    double pctWithHoisted = 0.0;
    if (m_loopsConsidered > 0)
    {
        pctWithHoisted = 100.0 * (double(m_loopsWithHoistedExpressions) / double(m_loopsConsidered));
    }
    double exprsPerLoopWithExpr = 0.0;
    if (m_loopsWithHoistedExpressions > 0)
    {
        exprsPerLoopWithExpr = double(m_totalHoistedExpressions) / double(m_loopsWithHoistedExpressions);
    }
    printf("Considered %d loops.  Of these, we hoisted expressions out of %d (%5.2f%%).\n", m_loopsConsidered,
           m_loopsWithHoistedExpressions, pctWithHoisted);
    printf("  A total of %d expressions were hoisted, an average of %5.2f per loop-with-hoisted-expr.\n",
           m_totalHoistedExpressions, exprsPerLoopWithExpr);
}
#endif // LOOP_HOIST_STATS

//------------------------------------------------------------------------
// RecordStateAtEndOfInlining: capture timing data (if enabled) after
// inlining as completed.
//
// Note:
// Records data needed for SQM and inlining data dumps.  Should be
// called after inlining is complete.  (We do this after inlining
// because this marks the last point at which the JIT is likely to
// cause type-loading and class initialization).

void Compiler::RecordStateAtEndOfInlining()
{
#if defined(DEBUG)
    m_compCyclesAtEndOfInlining = minipal_hires_ticks();
#endif // defined(DEBUG)
}

//------------------------------------------------------------------------
// RecordStateAtEndOfCompilation: capture timing data (if enabled) after
// compilation is completed.

void Compiler::RecordStateAtEndOfCompilation()
{
#if defined(DEBUG)
    m_compCycles = 0;

    int64_t lpCycles = minipal_hires_ticks();
    if (lpCycles > m_compCyclesAtEndOfInlining)
    {
        int64_t lpFreq = minipal_hires_tick_frequency();
        m_compCycles   = lpCycles - m_compCyclesAtEndOfInlining;
        m_compCycles *= 1000000;
        m_compCycles /= lpFreq;
    }
#endif // defined(DEBUG)
}

#if FUNC_INFO_LOGGING
// static
const char* Compiler::compJitFuncInfoFilename = nullptr;

// static
FILE* Compiler::compJitFuncInfoFile = nullptr;
#endif // FUNC_INFO_LOGGING

#ifdef DEBUG

// dumpConvertedVarSet() dumps the varset bits that are tracked
// variable indices, and we convert them to variable numbers, sort the variable numbers, and
// print them as variable numbers. To do this, we use a temporary set indexed by
// variable number. We can't use the "all varset" type because it is still size-limited, and might
// not be big enough to handle all possible variable numbers.
void dumpConvertedVarSet(Compiler* comp, VARSET_VALARG_TP vars)
{
    BYTE* pVarNumSet; // trivial set: one byte per varNum, 0 means not in set, 1 means in set.

    size_t varNumSetBytes = comp->lvaCount * sizeof(BYTE);
    pVarNumSet            = (BYTE*)_alloca(varNumSetBytes);
    memset(pVarNumSet, 0, varNumSetBytes); // empty the set

    VarSetOps::Iter iter(comp, vars);
    unsigned        varIndex = 0;
    while (iter.NextElem(&varIndex))
    {
        unsigned varNum    = comp->lvaTrackedIndexToLclNum(varIndex);
        pVarNumSet[varNum] = 1; // This varNum is in the set
    }

    bool first = true;
    printf("{");
    for (unsigned varNum = 0; varNum < comp->lvaCount; varNum++)
    {
        if (pVarNumSet[varNum] == 1)
        {
            if (!first)
            {
                printf(" ");
            }
            printf("V%02u", varNum);
            first = false;
        }
    }
    printf("}");
}

/*XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
XX                                                                           XX
XX                          Debugging helpers                                XX
XX                                                                           XX
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
*/

/*****************************************************************************/
/* The following functions are intended to be called from the debugger, to dump
 * various data structures.
 *
 * The versions that start with 'c' take a Compiler* as the first argument.
 * The versions that start with 'd' use the tlsCompiler, so don't require a Compiler*.
 *
 * Summary:
 *      cBlock,      dBlock         : Display a basic block (call fgTableDispBasicBlock()).
 *      cBlocks,     dBlocks        : Display all the basic blocks of a function (call fgDispBasicBlocks()).
 *      cBlocksV,    dBlocksV       : Display all the basic blocks of a function (call fgDispBasicBlocks(true)).
 *                                    "V" means "verbose", and will dump all the trees.
 *      cStmt,       dStmt          : Display a Statement (call gtDispStmt()).
 *      cTree,       dTree          : Display a tree (call gtDispTree()).
 *      cTreeLIR,    dTreeLIR       : Display a tree in LIR form (call gtDispLIRNode()).
 *      cTreeRange,  dTreeRange     : Display a range of trees in LIR form (call gtDispLIRNode()).
 *      cTrees,      dTrees         : Display all the trees in a function (call fgDumpTrees()).
 *      cEH,         dEH            : Display the EH handler table (call fgDispHandlerTab()).
 *      cVar,        dVar           : Display a local variable given its number (call lvaDumpEntry()).
 *      cVarDsc,     dVarDsc        : Display a local variable given a LclVarDsc* (call lvaDumpEntry()).
 *      cVars,       dVars          : Display the local variable table (call lvaTableDump()).
 *      cVarsFinal,  dVarsFinal     : Display the local variable table (call lvaTableDump(FINAL_FRAME_LAYOUT)).
 *      cBlockPreds, dBlockPreds    : Display a block's predecessors (call block->dspPreds()).
 *      cBlockSuccs, dBlockSuccs    : Display a block's successors (call block->dspSuccs()).
 *      cReach,      dReach         : Display all block reachability (call BlockReachabilitySets::Dump).
 *      cDoms,       dDoms          : Display all block dominators (call FlowGraphDominatorTree::Dump).
 *      cLiveness,   dLiveness      : Display per-block variable liveness (call fgDispBBLiveness()).
 *      cCVarSet,    dCVarSet       : Display a "converted" VARSET_TP: the varset is assumed to be tracked variable
 *                                    indices. These are converted to variable numbers and sorted. (Calls
 *                                    dumpConvertedVarSet()).
 *      cLoops,      dLoops         : Display the loops (call FlowGraphNaturalLoops::Dump()) with
 *                                    Compiler::m_loops.
 *      cLoopsA,     dLoopsA        : Display the loops (call FlowGraphNaturalLoops::Dump()) with a given
 *                                    loops arg.
 *      cLoop,       dLoop          : Display a single loop (call FlowGraphNaturalLoop::Dump()) with given
 *                                    loop arg.
 *      cTreeFlags,  dTreeFlags     : Display tree flags for a specified tree.
 *      cVN,         dVN            : Display a ValueNum (call vnPrint()).
 *
 * The following don't require a Compiler* to work:
 *      dRegMask                    : Display a regMaskTP (call dspRegMask(mask)).
 *      dBlockList                  : Display a BasicBlockList*.
 *      dIsa                        : Display a CORINFO_InstructionSet
 *      dIsaFlags                   : Display a CORINFO_InstructionSetFlags
 *
 * The following find an object in the IR and return it, as well as setting a global variable with the value that can
 * be used in the debugger (e.g., in the watch window, or as a way to get an address for data breakpoints).
 *      dFindTreeInTree             : Find a tree in a given tree, specifying a tree id. Sets `dbTree`.
 *      dFindTree                   : Find a tree in the IR, specifying a tree id. Sets `dbTree` and `dbTreeBlock`.
 *      dFindStmt                   : Find a Statement in the IR, specifying a statement id. Sets `dbStmt`.
 *      dFindBlock                  : Find a block in the IR, specifying a block number. Sets `dbBlock`.
 *      dFindLoop                   : Find a loop specifying a loop index. Sets `dbLoop`.
 */

// Make the debug helpers available (under #ifdef DEBUG) even though they are unreferenced. When the Microsoft
// linker is using /OPT:REF, it throws away unreferenced COMDATs (typically functions). Using "/INCLUDE:symbol"
// forces the linker to keep these anyway.
//
// Mark them `export "C"` so the names are not C++ name mangled. This makes it easier to refer to them in the
// `/INCLUDE` switch, and potentially makes them easier to find by a debugger.
//
// On x64/arm64, "C" names are not decorated (mangled). However, on x86, the names are decorated for `__stdcall`
// with a leading `_` and trailing `@N` for `N` bytes of arguments. To simplify, use __cdecl for x86. (Specifying
// it for other platforms is a no-op.)

#define JITDBGAPI extern "C"

#if defined(_MSC_VER)

#if defined(HOST_AMD64) || defined(HOST_ARM64)

// Functions which take a Compiler*
#pragma comment(linker, "/include:cBlock")
#pragma comment(linker, "/include:cBlocks")
#pragma comment(linker, "/include:cBlocksV")
#pragma comment(linker, "/include:cStmt")
#pragma comment(linker, "/include:cTree")
#pragma comment(linker, "/include:cTreeLIR")
#pragma comment(linker, "/include:cTreeRange")
#pragma comment(linker, "/include:cTrees")
#pragma comment(linker, "/include:cEH")
#pragma comment(linker, "/include:cVar")
#pragma comment(linker, "/include:cVarDsc")
#pragma comment(linker, "/include:cVars")
#pragma comment(linker, "/include:cVarsFinal")
#pragma comment(linker, "/include:cBlockPreds")
#pragma comment(linker, "/include:cBlockSuccs")
#pragma comment(linker, "/include:cReach")
#pragma comment(linker, "/include:cDoms")
#pragma comment(linker, "/include:cLiveness")
#pragma comment(linker, "/include:cCVarSet")
#pragma comment(linker, "/include:cLoops")
#pragma comment(linker, "/include:cLoopsA")
#pragma comment(linker, "/include:cLoop")
#pragma comment(linker, "/include:cScev")
#pragma comment(linker, "/include:cTreeFlags")
#pragma comment(linker, "/include:cVN")

// Functions which call the c* functions getting Compiler* using `JitTls::GetCompiler()`
#pragma comment(linker, "/include:dBlock")
#pragma comment(linker, "/include:dBlocks")
#pragma comment(linker, "/include:dBlocksV")
#pragma comment(linker, "/include:dStmt")
#pragma comment(linker, "/include:dTree")
#pragma comment(linker, "/include:dTreeLIR")
#pragma comment(linker, "/include:dTreeRange")
#pragma comment(linker, "/include:dTrees")
#pragma comment(linker, "/include:dEH")
#pragma comment(linker, "/include:dVar")
#pragma comment(linker, "/include:dVarDsc")
#pragma comment(linker, "/include:dVars")
#pragma comment(linker, "/include:dVarsFinal")
#pragma comment(linker, "/include:dBlockPreds")
#pragma comment(linker, "/include:dBlockSuccs")
#pragma comment(linker, "/include:dReach")
#pragma comment(linker, "/include:dDoms")
#pragma comment(linker, "/include:dLiveness")
#pragma comment(linker, "/include:dCVarSet")
#pragma comment(linker, "/include:dLoop")
#pragma comment(linker, "/include:dLoops")
#pragma comment(linker, "/include:dScev")
#pragma comment(linker, "/include:dTreeFlags")
#pragma comment(linker, "/include:dVN")

// Functions which don't require a Compiler*
#pragma comment(linker, "/include:dRegMask")
#pragma comment(linker, "/include:dBlockList")
#pragma comment(linker, "/include:dIsa")
#pragma comment(linker, "/include:dIsaFlags")

// Functions which search for objects in the IR
#pragma comment(linker, "/include:dFindTreeInTree")
#pragma comment(linker, "/include:dFindTree")
#pragma comment(linker, "/include:dFindStmt")
#pragma comment(linker, "/include:dFindBlock")
#pragma comment(linker, "/include:dFindLoop")

#elif defined(HOST_X86)

// Functions which take a Compiler*
#pragma comment(linker, "/include:_cBlock")
#pragma comment(linker, "/include:_cBlocks")
#pragma comment(linker, "/include:_cBlocksV")
#pragma comment(linker, "/include:_cStmt")
#pragma comment(linker, "/include:_cTree")
#pragma comment(linker, "/include:_cTreeLIR")
#pragma comment(linker, "/include:_cTreeRange")
#pragma comment(linker, "/include:_cTrees")
#pragma comment(linker, "/include:_cEH")
#pragma comment(linker, "/include:_cVar")
#pragma comment(linker, "/include:_cVarDsc")
#pragma comment(linker, "/include:_cVars")
#pragma comment(linker, "/include:_cVarsFinal")
#pragma comment(linker, "/include:_cBlockPreds")
#pragma comment(linker, "/include:_cBlockSuccs")
#pragma comment(linker, "/include:_cReach")
#pragma comment(linker, "/include:_cDoms")
#pragma comment(linker, "/include:_cLiveness")
#pragma comment(linker, "/include:_cCVarSet")
#pragma comment(linker, "/include:_cLoop")
#pragma comment(linker, "/include:_cLoops")
#pragma comment(linker, "/include:_cLoopsA")
#pragma comment(linker, "/include:_cTreeFlags")
#pragma comment(linker, "/include:_cVN")

// Functions which call the c* functions getting Compiler* using `JitTls:_:GetCompiler()`
#pragma comment(linker, "/include:_dBlock")
#pragma comment(linker, "/include:_dBlocks")
#pragma comment(linker, "/include:_dBlocksV")
#pragma comment(linker, "/include:_dStmt")
#pragma comment(linker, "/include:_dTree")
#pragma comment(linker, "/include:_dTreeLIR")
#pragma comment(linker, "/include:_dTreeRange")
#pragma comment(linker, "/include:_dTrees")
#pragma comment(linker, "/include:_dEH")
#pragma comment(linker, "/include:_dVar")
#pragma comment(linker, "/include:_dVarDsc")
#pragma comment(linker, "/include:_dVars")
#pragma comment(linker, "/include:_dVarsFinal")
#pragma comment(linker, "/include:_dBlockPreds")
#pragma comment(linker, "/include:_dBlockSuccs")
#pragma comment(linker, "/include:_dReach")
#pragma comment(linker, "/include:_dDoms")
#pragma comment(linker, "/include:_dLiveness")
#pragma comment(linker, "/include:_dCVarSet")
#pragma comment(linker, "/include:_dLoops")
#pragma comment(linker, "/include:_dLoopsA")
#pragma comment(linker, "/include:_dLoop")
#pragma comment(linker, "/include:_dTreeFlags")
#pragma comment(linker, "/include:_dVN")

// Functions which don't require a Compiler*
#pragma comment(linker, "/include:_dRegMask")
#pragma comment(linker, "/include:_dBlockList")

// Functions which search for objects in the IR
#pragma comment(linker, "/include:_dFindTreeInTree")
#pragma comment(linker, "/include:_dFindTree")
#pragma comment(linker, "/include:_dFindStmt")
#pragma comment(linker, "/include:_dFindBlock")
#pragma comment(linker, "/include:_dFindLoop")

#endif // HOST_*

#endif // _MSC_VER

JITDBGAPI void __cdecl cBlock(Compiler* comp, BasicBlock* block)
{
    static unsigned sequenceNumber = 0; // separate calls with a number to indicate this function has been called
    printf("===================================================================== *Block %u\n", sequenceNumber++);
    comp->fgTableDispBasicBlock(block);
}

JITDBGAPI void __cdecl cBlocks(Compiler* comp)
{
    static unsigned sequenceNumber = 0; // separate calls with a number to indicate this function has been called
    printf("===================================================================== *Blocks %u\n", sequenceNumber++);
    comp->fgDispBasicBlocks();
}

JITDBGAPI void __cdecl cBlocksV(Compiler* comp)
{
    static unsigned sequenceNumber = 0; // separate calls with a number to indicate this function has been called
    printf("===================================================================== *BlocksV %u\n", sequenceNumber++);
    comp->fgDispBasicBlocks(true);
}

JITDBGAPI void __cdecl cStmt(Compiler* comp, Statement* statement)
{
    static unsigned sequenceNumber = 0; // separate calls with a number to indicate this function has been called
    printf("===================================================================== *Stmt %u\n", sequenceNumber++);
    comp->gtDispStmt(statement, ">>>");
}

JITDBGAPI void __cdecl cTree(Compiler* comp, GenTree* tree)
{
    static unsigned sequenceNumber = 0; // separate calls with a number to indicate this function has been called
    printf("===================================================================== *Tree %u\n", sequenceNumber++);
    comp->gtDispTree(tree, nullptr, ">>>");
}

JITDBGAPI void __cdecl cTreeLIR(Compiler* comp, GenTree* tree)
{
    static unsigned sequenceNumber = 0; // separate calls with a number to indicate this function has been called
    printf("===================================================================== *TreeLIR %u\n", sequenceNumber++);
    comp->gtDispLIRNode(tree);
}

JITDBGAPI void __cdecl cTreeRange(Compiler* comp, GenTree* first, GenTree* last)
{
    static unsigned sequenceNumber = 0; // separate calls with a number to indicate this function has been called
    printf("===================================================================== *TreeRange %u\n", sequenceNumber++);
    GenTree* cur = first;
    while (true)
    {
        comp->gtDispLIRNode(cur);
        if (cur == last)
            break;

        cur = cur->gtNext;
    }
}

JITDBGAPI void __cdecl cTrees(Compiler* comp)
{
    static unsigned sequenceNumber = 0; // separate calls with a number to indicate this function has been called
    printf("===================================================================== *Trees %u\n", sequenceNumber++);
    comp->fgDumpTrees(comp->fgFirstBB, nullptr);
}

JITDBGAPI void __cdecl cEH(Compiler* comp)
{
    static unsigned sequenceNumber = 0; // separate calls with a number to indicate this function has been called
    printf("===================================================================== *EH %u\n", sequenceNumber++);
    comp->fgDispHandlerTab();
}

JITDBGAPI void __cdecl cVar(Compiler* comp, unsigned lclNum)
{
    static unsigned sequenceNumber = 0; // separate calls with a number to indicate this function has been called
    printf("===================================================================== *Var %u\n", sequenceNumber++);
    comp->lvaDumpEntry(lclNum, Compiler::FINAL_FRAME_LAYOUT);
}

JITDBGAPI void __cdecl cVarDsc(Compiler* comp, LclVarDsc* varDsc)
{
    static unsigned sequenceNumber = 0; // separate calls with a number to indicate this function has been called
    printf("===================================================================== *VarDsc %u\n", sequenceNumber++);
    unsigned lclNum = comp->lvaGetLclNum(varDsc);
    comp->lvaDumpEntry(lclNum, Compiler::FINAL_FRAME_LAYOUT);
}

JITDBGAPI void __cdecl cVars(Compiler* comp)
{
    static unsigned sequenceNumber = 0; // separate calls with a number to indicate this function has been called
    printf("===================================================================== *Vars %u\n", sequenceNumber++);
    comp->lvaTableDump();
}

JITDBGAPI void __cdecl cVarsFinal(Compiler* comp)
{
    static unsigned sequenceNumber = 0; // separate calls with a number to indicate this function has been called
    printf("===================================================================== *Vars %u\n", sequenceNumber++);
    comp->lvaTableDump(Compiler::FINAL_FRAME_LAYOUT);
}

JITDBGAPI void __cdecl cBlockPreds(Compiler* comp, BasicBlock* block)
{
    static unsigned sequenceNumber = 0; // separate calls with a number to indicate this function has been called
    printf("===================================================================== *BlockPreds %u\n", sequenceNumber++);
    block->dspPreds();
}

JITDBGAPI void __cdecl cBlockSuccs(Compiler* comp, BasicBlock* block)
{
    static unsigned sequenceNumber = 0; // separate calls with a number to indicate this function has been called
    printf("===================================================================== *BlockSuccs %u\n", sequenceNumber++);
    block->dspSuccs();
}

JITDBGAPI void __cdecl cReach(Compiler* comp)
{
    static unsigned sequenceNumber = 0; // separate calls with a number to indicate this function has been called
    printf("===================================================================== *Reach %u\n", sequenceNumber++);
    if (comp->m_reachabilitySets != nullptr)
    {
        comp->m_reachabilitySets->Dump();
    }
    else
    {
        printf("  Not computed\n");
    }
}

JITDBGAPI void __cdecl cDoms(Compiler* comp)
{
    static unsigned sequenceNumber = 0; // separate calls with a number to indicate this function has been called
    printf("===================================================================== *Doms %u\n", sequenceNumber++);
    if (comp->m_domTree != nullptr)
    {
        comp->m_domTree->Dump();
    }
    else
    {
        printf("  Not computed\n");
    }
}

JITDBGAPI void __cdecl cLiveness(Compiler* comp)
{
    static unsigned sequenceNumber = 0; // separate calls with a number to indicate this function has been called
    printf("===================================================================== *Liveness %u\n", sequenceNumber++);
    comp->fgDispBBLiveness();
}

JITDBGAPI void __cdecl cCVarSet(Compiler* comp, VARSET_VALARG_TP vars)
{
    static unsigned sequenceNumber = 0; // separate calls with a number to indicate this function has been called
    printf("===================================================================== *CVarSet %u\n", sequenceNumber++);
    dumpConvertedVarSet(comp, vars);
    printf("\n"); // dumpConvertedVarSet() doesn't emit a trailing newline
}

JITDBGAPI void __cdecl cLoops(Compiler* comp)
{
    static unsigned sequenceNumber = 0; // separate calls with a number to indicate this function has been called
    printf("===================================================================== *Loops %u\n", sequenceNumber++);
    FlowGraphNaturalLoops::Dump(comp->m_loops);
}

JITDBGAPI void __cdecl cLoopsA(Compiler* comp, FlowGraphNaturalLoops* loops)
{
    static unsigned sequenceNumber = 0; // separate calls with a number to indicate this function has been called
    printf("===================================================================== *LoopsA %u\n", sequenceNumber++);
    FlowGraphNaturalLoops::Dump(loops);
}

JITDBGAPI void __cdecl cLoop(Compiler* comp, FlowGraphNaturalLoop* loop)
{
    static unsigned sequenceNumber = 0; // separate calls with a number to indicate this function has been called
    printf("===================================================================== *Loop %u\n", sequenceNumber++);
    FlowGraphNaturalLoop::Dump(loop);
}

JITDBGAPI void __cdecl cScev(Compiler* comp, Scev* scev)
{
    static unsigned sequenceNumber = 0; // separate calls with a number to indicate this function has been called
    printf("===================================================================== *Scev %u\n", sequenceNumber++);
    if (scev == nullptr)
    {
        printf("  NULL\n");
    }
    else
    {
        scev->Dump(comp);
        printf("\n");
    }
}

JITDBGAPI void __cdecl cTreeFlags(Compiler* comp, GenTree* tree)
{
    static unsigned sequenceNumber = 0; // separate calls with a number to indicate this function has been called
    printf("===================================================================== *TreeFlags %u\n", sequenceNumber++);

    int chars = 0;

    if (tree->gtFlags != 0)
    {
        chars += printf("flags=");

        // Node flags

#if defined(DEBUG)
        if (tree->gtDebugFlags & GTF_DEBUG_NODE_LARGE)
        {
            chars += printf("[NODE_LARGE]");
        }
        if (tree->gtDebugFlags & GTF_DEBUG_NODE_SMALL)
        {
            chars += printf("[NODE_SMALL]");
        }
        if (tree->gtDebugFlags & GTF_DEBUG_NODE_MORPHED)
        {
            chars += printf("[MORPHED]");
        }
#endif // defined(DEBUG)

        if (tree->gtFlags & GTF_COLON_COND)
        {
            chars += printf("[COLON_COND]");
        }

        // Operator flags

        genTreeOps op = tree->OperGet();
        switch (op)
        {
            case GT_LCL_VAR:
            case GT_LCL_FLD:
            case GT_LCL_ADDR:
            case GT_STORE_LCL_FLD:
            case GT_STORE_LCL_VAR:
                if (tree->gtFlags & GTF_VAR_DEF)
                {
                    chars += printf("[VAR_DEF]");
                }
                if (tree->gtFlags & GTF_VAR_USEASG)
                {
                    chars += printf("[VAR_USEASG]");
                }
                if (tree->gtFlags & GTF_VAR_MOREUSES)
                {
                    chars += printf("[VAR_MOREUSES]");
                }
                if (!comp->lvaGetDesc(tree->AsLclVarCommon())->lvPromoted)
                {
                    if (tree->gtFlags & GTF_VAR_DEATH)
                    {
                        chars += printf("[VAR_DEATH]");
                    }
                }
                else
                {
                    if (tree->gtFlags & GTF_VAR_FIELD_DEATH0)
                    {
                        chars += printf("[VAR_FIELD_DEATH0]");
                    }
                }
                if (tree->gtFlags & GTF_VAR_FIELD_DEATH1)
                {
                    chars += printf("[VAR_FIELD_DEATH1]");
                }
                if (tree->gtFlags & GTF_VAR_FIELD_DEATH2)
                {
                    chars += printf("[VAR_FIELD_DEATH2]");
                }
                if (tree->gtFlags & GTF_VAR_FIELD_DEATH3)
                {
                    chars += printf("[VAR_FIELD_DEATH3]");
                }
                if (tree->gtFlags & GTF_VAR_EXPLICIT_INIT)
                {
                    chars += printf("[VAR_EXPLICIT_INIT]");
                }
#if defined(DEBUG)
                if (tree->gtDebugFlags & GTF_DEBUG_VAR_CSE_REF)
                {
                    chars += printf("[VAR_CSE_REF]");
                }
#endif
                break;

            case GT_NO_OP:
                break;

            case GT_INDEX_ADDR:
                if (tree->gtFlags & GTF_INX_RNGCHK)
                {
                    chars += printf("[INX_RNGCHK]");
                }
                break;

            case GT_IND:
            case GT_STOREIND:

                if (tree->gtFlags & GTF_IND_VOLATILE)
                {
                    chars += printf("[IND_VOLATILE]");
                }
                if (tree->gtFlags & GTF_IND_TGT_NOT_HEAP)
                {
                    chars += printf("[IND_TGT_NOT_HEAP]");
                }
                if (tree->gtFlags & GTF_IND_TGT_HEAP)
                {
                    chars += printf("[IND_TGT_HEAP]");
                }
                if (tree->gtFlags & GTF_IND_REQ_ADDR_IN_REG)
                {
                    chars += printf("[IND_REQ_ADDR_IN_REG]");
                }
                if (tree->gtFlags & GTF_IND_UNALIGNED)
                {
                    chars += printf("[IND_UNALIGNED]");
                }
                if (tree->gtFlags & GTF_IND_INVARIANT)
                {
                    chars += printf("[IND_INVARIANT]");
                }
                if (tree->gtFlags & GTF_IND_NONNULL)
                {
                    chars += printf("[IND_NONNULL]");
                }
                if (tree->gtFlags & GTF_IND_INITCLASS)
                {
                    chars += printf("[IND_INITCLASS]");
                }
                break;

            case GT_MUL:
#if !defined(TARGET_64BIT)
            case GT_MUL_LONG:
#endif

                if (tree->gtFlags & GTF_MUL_64RSLT)
                {
                    chars += printf("[64RSLT]");
                }
                if (tree->gtFlags & GTF_ADDRMODE_NO_CSE)
                {
                    chars += printf("[ADDRMODE_NO_CSE]");
                }
                break;

            case GT_ADD:

                if (tree->gtFlags & GTF_ADDRMODE_NO_CSE)
                {
                    chars += printf("[ADDRMODE_NO_CSE]");
                }
                break;

            case GT_LSH:

                if (tree->gtFlags & GTF_ADDRMODE_NO_CSE)
                {
                    chars += printf("[ADDRMODE_NO_CSE]");
                }
                break;

            case GT_MOD:
            case GT_UMOD:
                break;

            case GT_EQ:
            case GT_NE:
            case GT_LT:
            case GT_LE:
            case GT_GT:
            case GT_GE:

                if (tree->gtFlags & GTF_RELOP_NAN_UN)
                {
                    chars += printf("[RELOP_NAN_UN]");
                }
                if (tree->gtFlags & GTF_RELOP_JMP_USED)
                {
                    chars += printf("[RELOP_JMP_USED]");
                }
                break;

            case GT_BOX:

                if (tree->gtFlags & GTF_BOX_VALUE)
                {
                    chars += printf("[BOX_VALUE]");
                }

                if (tree->gtFlags & GTF_BOX_CLONED)
                {
                    chars += printf("[BOX_CLONED]");
                }
                break;

            case GT_ARR_ADDR:
                if (tree->gtFlags & GTF_ARR_ADDR_NONNULL)
                {
                    chars += printf("[ARR_ADDR_NONNULL]");
                }
                break;

            case GT_CNS_INT:
            {
                GenTreeFlags handleKind = (tree->gtFlags & GTF_ICON_HDL_MASK);
                if (handleKind != 0)
                {
                    chars += printf("[%s]", GenTree::gtGetHandleKindString(handleKind));
                }
            }
            break;

            case GT_BLK:
            case GT_STORE_BLK:

                if (tree->gtFlags & GTF_IND_VOLATILE)
                {
                    chars += printf("[IND_VOLATILE]");
                }
                if (tree->gtFlags & GTF_IND_UNALIGNED)
                {
                    chars += printf("[IND_UNALIGNED]");
                }
                break;

            case GT_CALL:

                if (tree->gtFlags & GTF_CALL_UNMANAGED)
                {
                    chars += printf("[CALL_UNMANAGED]");
                }
                if (tree->gtFlags & GTF_CALL_INLINE_CANDIDATE)
                {
                    chars += printf("[CALL_INLINE_CANDIDATE]");
                }
                if (!tree->AsCall()->IsVirtual())
                {
                    chars += printf("[CALL_NONVIRT]");
                }
                if (tree->AsCall()->IsVirtualVtable())
                {
                    chars += printf("[CALL_VIRT_VTABLE]");
                }
                if (tree->AsCall()->IsVirtualStub())
                {
                    chars += printf("[CALL_VIRT_STUB]");
                }
                if (tree->gtFlags & GTF_CALL_NULLCHECK)
                {
                    chars += printf("[CALL_NULLCHECK]");
                }
                if (tree->gtFlags & GTF_CALL_POP_ARGS)
                {
                    chars += printf("[CALL_POP_ARGS]");
                }
                if (tree->gtFlags & GTF_CALL_HOISTABLE)
                {
                    chars += printf("[CALL_HOISTABLE]");
                }

                // More flags associated with calls.

                {
                    GenTreeCall* call = tree->AsCall();

                    if (call->gtCallMoreFlags & GTF_CALL_M_EXPLICIT_TAILCALL)
                    {
                        chars += printf("[CALL_M_EXPLICIT_TAILCALL]");
                    }
                    if (call->gtCallMoreFlags & GTF_CALL_M_TAILCALL)
                    {
                        chars += printf("[CALL_M_TAILCALL]");
                    }
                    if (call->gtCallMoreFlags & GTF_CALL_M_RETBUFFARG)
                    {
                        chars += printf("[CALL_M_RETBUFFARG]");
                    }
                    if (call->gtCallMoreFlags & GTF_CALL_M_DELEGATE_INV)
                    {
                        chars += printf("[CALL_M_DELEGATE_INV]");
                    }
                    if (call->gtCallMoreFlags & GTF_CALL_M_NOGCCHECK)
                    {
                        chars += printf("[CALL_M_NOGCCHECK]");
                    }
                    if (call->IsSpecialIntrinsic())
                    {
                        chars += printf("[CALL_M_SPECIAL_INTRINSIC]");
                    }

                    if (call->IsVirtualStub())
                    {
                        if (call->gtCallMoreFlags & GTF_CALL_M_VIRTSTUB_REL_INDIRECT)
                        {
                            chars += printf("[CALL_M_VIRTSTUB_REL_INDIRECT]");
                        }
                    }
                    else if (!call->IsVirtual())
                    {
                        if (call->gtCallMoreFlags & GTF_CALL_M_NONVIRT_SAME_THIS)
                        {
                            chars += printf("[CALL_M_NONVIRT_SAME_THIS]");
                        }
                    }

                    if (call->gtCallMoreFlags & GTF_CALL_M_FRAME_VAR_DEATH)
                    {
                        chars += printf("[CALL_M_FRAME_VAR_DEATH]");
                    }
                    if (call->gtCallMoreFlags & GTF_CALL_M_TAILCALL_VIA_JIT_HELPER)
                    {
                        chars += printf("[CALL_M_TAILCALL_VIA_JIT_HELPER]");
                    }
#if FEATURE_TAILCALL_OPT
                    if (call->gtCallMoreFlags & GTF_CALL_M_IMPLICIT_TAILCALL)
                    {
                        chars += printf("[CALL_M_IMPLICIT_TAILCALL]");
                    }
#endif
                    if (call->gtCallMoreFlags & GTF_CALL_M_PINVOKE)
                    {
                        chars += printf("[CALL_M_PINVOKE]");
                    }

                    if (call->IsFatPointerCandidate())
                    {
                        chars += printf("[CALL_FAT_POINTER_CANDIDATE]");
                    }

                    if (call->IsGuarded())
                    {
                        chars += printf("[CALL_GUARDED]");
                    }

                    if (call->gtCallDebugFlags & GTF_CALL_MD_STRESS_TAILCALL)
                    {
                        chars += printf("[CALL_MD_STRESS_TAILCALL]");
                    }

                    if (call->gtCallDebugFlags & GTF_CALL_MD_DEVIRTUALIZED)
                    {
                        chars += printf("[CALL_MD_DEVIRTUALIZED]");
                    }

                    if (call->gtCallDebugFlags & GTF_CALL_MD_GUARDED)
                    {
                        chars += printf("[CALL_MD_GUARDED]");
                    }

                    if (call->gtCallDebugFlags & GTF_CALL_MD_UNBOXED)
                    {
                        chars += printf("[CALL_MD_UNBOXED]");
                    }
                }
                break;
            default:

            {
                GenTreeFlags flags = (tree->gtFlags & (~(GTF_COMMON_MASK | GTF_OVERFLOW)));
                if (flags != 0)
                {
                    chars += printf("[%08X]", flags);
                }
            }
            break;
        }

        // Common flags.

        if (tree->gtFlags & GTF_ASG)
        {
            chars += printf("[ASG]");
        }
        if (tree->gtFlags & GTF_CALL)
        {
            chars += printf("[CALL]");
        }
        switch (op)
        {
            case GT_MUL:
            case GT_CAST:
            case GT_ADD:
            case GT_SUB:
                if (tree->gtFlags & GTF_OVERFLOW)
                {
                    chars += printf("[OVERFLOW]");
                }
                break;
            default:
                break;
        }
        if (tree->gtFlags & GTF_EXCEPT)
        {
            chars += printf("[EXCEPT]");
        }
        if (tree->gtFlags & GTF_GLOB_REF)
        {
            chars += printf("[GLOB_REF]");
        }
        if (tree->gtFlags & GTF_ORDER_SIDEEFF)
        {
            chars += printf("[ORDER_SIDEEFF]");
        }
        if (tree->gtFlags & GTF_REVERSE_OPS)
        {
            if (op != GT_LCL_VAR)
            {
                chars += printf("[REVERSE_OPS]");
            }
        }
        if (tree->gtFlags & GTF_SPILLED)
        {
            chars += printf("[SPILLED_OPER]");
        }
        if (tree->gtFlags & GTF_SET_FLAGS)
        {
            chars += printf("[SET_FLAGS]");
        }
        if (tree->gtFlags & GTF_IND_NONFAULTING)
        {
            if (tree->OperIsIndirOrArrMetaData())
            {
                chars += printf("[IND_NONFAULTING]");
            }
        }
        if (tree->gtFlags & GTF_MAKE_CSE)
        {
            chars += printf("[MAKE_CSE]");
        }
        if (tree->gtFlags & GTF_DONT_CSE)
        {
            chars += printf("[DONT_CSE]");
        }
        if (tree->gtFlags & GTF_UNSIGNED)
        {
            chars += printf("[SMALL_UNSIGNED]");
        }
        if (tree->gtFlags & GTF_SPILL)
        {
            chars += printf("[SPILL]");
        }
        if (tree->gtFlags & GTF_REUSE_REG_VAL)
        {
            if (op == GT_CNS_INT)
            {
                chars += printf("[REUSE_REG_VAL]");
            }
        }
    }
}

JITDBGAPI void __cdecl cVN(Compiler* comp, ValueNum vn)
{
    static unsigned sequenceNumber = 0; // separate calls with a number to indicate this function has been called
    printf("===================================================================== *VN %u\n", sequenceNumber++);
    comp->vnPrint(vn, 1);
    printf("\n");
}

JITDBGAPI void __cdecl dBlock(BasicBlock* block)
{
    cBlock(JitTls::GetCompiler(), block);
}

JITDBGAPI void __cdecl dBlocks()
{
    cBlocks(JitTls::GetCompiler());
}

JITDBGAPI void __cdecl dBlocksV()
{
    cBlocksV(JitTls::GetCompiler());
}

JITDBGAPI void __cdecl dStmt(Statement* statement)
{
    cStmt(JitTls::GetCompiler(), statement);
}

JITDBGAPI void __cdecl dTree(GenTree* tree)
{
    cTree(JitTls::GetCompiler(), tree);
}

JITDBGAPI void __cdecl dTreeLIR(GenTree* tree)
{
    cTreeLIR(JitTls::GetCompiler(), tree);
}

JITDBGAPI void __cdecl dTreeRange(GenTree* first, GenTree* last)
{
    cTreeRange(JitTls::GetCompiler(), first, last);
}

JITDBGAPI void __cdecl dTrees()
{
    cTrees(JitTls::GetCompiler());
}

JITDBGAPI void __cdecl dEH()
{
    cEH(JitTls::GetCompiler());
}

JITDBGAPI void __cdecl dVar(unsigned lclNum)
{
    cVar(JitTls::GetCompiler(), lclNum);
}

JITDBGAPI void __cdecl dVarDsc(LclVarDsc* varDsc)
{
    cVarDsc(JitTls::GetCompiler(), varDsc);
}

JITDBGAPI void __cdecl dVars()
{
    cVars(JitTls::GetCompiler());
}

JITDBGAPI void __cdecl dVarsFinal()
{
    cVarsFinal(JitTls::GetCompiler());
}

JITDBGAPI void __cdecl dBlockPreds(BasicBlock* block)
{
    cBlockPreds(JitTls::GetCompiler(), block);
}

JITDBGAPI void __cdecl dBlockSuccs(BasicBlock* block)
{
    cBlockSuccs(JitTls::GetCompiler(), block);
}

JITDBGAPI void __cdecl dReach()
{
    cReach(JitTls::GetCompiler());
}

JITDBGAPI void __cdecl dDoms()
{
    cDoms(JitTls::GetCompiler());
}

JITDBGAPI void __cdecl dLiveness()
{
    cLiveness(JitTls::GetCompiler());
}

JITDBGAPI void __cdecl dCVarSet(VARSET_VALARG_TP vars)
{
    cCVarSet(JitTls::GetCompiler(), vars);
}

JITDBGAPI void __cdecl dLoops()
{
    cLoops(JitTls::GetCompiler());
}

JITDBGAPI void __cdecl dLoopsA(FlowGraphNaturalLoops* loops)
{
    cLoopsA(JitTls::GetCompiler(), loops);
}

JITDBGAPI void __cdecl dLoop(FlowGraphNaturalLoop* loop)
{
    cLoop(JitTls::GetCompiler(), loop);
}

JITDBGAPI void __cdecl dScev(Scev* scev)
{
    cScev(JitTls::GetCompiler(), scev);
}

JITDBGAPI void __cdecl dTreeFlags(GenTree* tree)
{
    cTreeFlags(JitTls::GetCompiler(), tree);
}

JITDBGAPI void __cdecl dVN(ValueNum vn)
{
    cVN(JitTls::GetCompiler(), vn);
}

JITDBGAPI void __cdecl dRegMask(const regMaskTP& mask)
{
    static unsigned sequenceNumber = 0; // separate calls with a number to indicate this function has been called
    printf("===================================================================== dRegMask %u\n", sequenceNumber++);
    dspRegMask(mask);
    printf("\n"); // dspRegMask() doesn't emit a trailing newline
}

JITDBGAPI void __cdecl dBlockList(BasicBlockList* list)
{
    static unsigned sequenceNumber = 0; // separate calls with a number to indicate this function has been called
    printf("===================================================================== dBlockList %u\n", sequenceNumber++);
    while (list != nullptr)
    {
        printf(FMT_BB " ", list->block->bbNum);
        list = list->next;
    }
    printf("\n");
}

JITDBGAPI void __cdecl dIsa(const CORINFO_InstructionSet isa)
{
    static unsigned sequenceNumber = 0; // separate calls with a number to indicate this function has been called
    printf("===================================================================== dIsa %u\n", sequenceNumber++);
    printf("%s\n", InstructionSetToString(isa));
}

JITDBGAPI void __cdecl dIsaFlags(const CORINFO_InstructionSetFlags& isaFlags)
{
    static unsigned sequenceNumber = 0; // separate calls with a number to indicate this function has been called
    printf("===================================================================== dIsaFlags %u\n", sequenceNumber++);
    if (isaFlags.IsEmpty())
    {
        printf("<empty>\n");
    }
    else
    {
        bool first = true;
        // The flags start at '1'. We don't know the last flag, so compute the maximum.
        // It would be better if CORINFO_InstructionSet defined InstructionSet_FIRST and InstructionSet_LAST,
        // or even better, if CORINFO_InstructionSetFlags defined an iterator over the instruction sets in the flags.
        CORINFO_InstructionSet isaFirst = (CORINFO_InstructionSet)1;
        CORINFO_InstructionSet isaLast  = (CORINFO_InstructionSet)(sizeof(CORINFO_InstructionSetFlags) * 8 - 1);
        for (CORINFO_InstructionSet isa = isaFirst; isa <= isaLast; isa = (CORINFO_InstructionSet)((int)isa + 1))
        {
            if (isaFlags.HasInstructionSet(isa))
            {
                printf("%s%s", first ? "" : " ", InstructionSetToString(isa));
                first = false;
            }
        }
    }
    printf("\n");
}

// Global variables available in debug mode.  That are set by debug APIs for finding
// Trees, Stmts, and/or Blocks using id or bbNum.
// That can be used in watch window or as a way to get address of fields for data breakpoints.

GenTree*              dbTree;
Statement*            dbStmt;
BasicBlock*           dbTreeBlock;
BasicBlock*           dbBlock;
FlowGraphNaturalLoop* dbLoop;

// Debug APIs for finding Trees, Stmts, and/or Blocks.
// As a side effect, they set the debug variables above.

JITDBGAPI GenTree* __cdecl dFindTreeInTree(GenTree* tree, unsigned id)
{
    if (tree == nullptr)
    {
        return nullptr;
    }

    if (tree->gtTreeID == id)
    {
        dbTree = tree;
        return tree;
    }

    GenTree* child = nullptr;
    tree->VisitOperands([&child, id](GenTree* operand) -> GenTree::VisitResult {
        child = dFindTreeInTree(child, id);
        return (child != nullptr) ? GenTree::VisitResult::Abort : GenTree::VisitResult::Continue;
    });

    return child;
}

JITDBGAPI GenTree* __cdecl dFindTree(unsigned id)
{
    Compiler* comp = JitTls::GetCompiler();

    dbTreeBlock = nullptr;
    dbTree      = nullptr;

    for (BasicBlock* const block : comp->Blocks())
    {
        for (Statement* const stmt : block->Statements())
        {
            GenTree* const tree = dFindTreeInTree(stmt->GetRootNode(), id);
            if (tree != nullptr)
            {
                dbTreeBlock = block;
                dbTree      = tree;
                return tree;
            }
        }
    }

    return nullptr;
}

JITDBGAPI Statement* __cdecl dFindStmt(unsigned id)
{
    Compiler* comp = JitTls::GetCompiler();

    dbStmt = nullptr;

    unsigned stmtId = 0;
    for (BasicBlock* const block : comp->Blocks())
    {
        for (Statement* const stmt : block->Statements())
        {
            stmtId++;
            if (stmtId == id)
            {
                dbStmt = stmt;
                return stmt;
            }
        }
    }

    return nullptr;
}

JITDBGAPI BasicBlock* __cdecl dFindBlock(unsigned bbNum)
{
    Compiler* comp = JitTls::GetCompiler();

    dbBlock = nullptr;

    for (BasicBlock* const block : comp->Blocks())
    {
        if (block->bbNum == bbNum)
        {
            dbBlock = block;
            return block;
        }
    }

    return nullptr;
}

JITDBGAPI FlowGraphNaturalLoop* __cdecl dFindLoop(unsigned index)
{
    Compiler* comp = JitTls::GetCompiler();
    dbLoop         = nullptr;

    if ((comp->m_loops == nullptr) || (index >= comp->m_loops->NumLoops()))
    {
        printf("Index %u out of range\n", index);
        return nullptr;
    }

    dbLoop = comp->m_loops->GetLoopByIndex(index);
    return dbLoop;
}

#endif // DEBUG

#if VARSET_COUNTOPS
// static
BitSetSupport::BitSetOpCounter Compiler::m_varsetOpCounter("VarSetOpCounts.log");
#endif
#if ALLVARSET_COUNTOPS
// static
BitSetSupport::BitSetOpCounter Compiler::m_allvarsetOpCounter("AllVarSetOpCounts.log");
#endif

// static
HelperCallProperties Compiler::s_helperCallProperties;

/*****************************************************************************/
/*****************************************************************************/

//------------------------------------------------------------------------
// killGCRefs:
// Given some tree node return does it need all GC refs to be spilled from
// callee save registers.
//
// Arguments:
//    tree       - the tree for which we ask about gc refs.
//
// Return Value:
//    true       - tree kills GC refs on callee save registers
//    false      - tree doesn't affect GC refs on callee save registers
//
bool Compiler::killGCRefs(GenTree* tree)
{
    if (tree->IsCall())
    {
        GenTreeCall* call = tree->AsCall();
        if (call->IsUnmanaged())
        {
            return true;
        }

        if (call->gtCallMethHnd == eeFindHelper(CORINFO_HELP_JIT_PINVOKE_BEGIN))
        {
            assert(opts.ShouldUsePInvokeHelpers());
            return true;
        }
    }
    else if (tree->OperIs(GT_START_PREEMPTGC))
    {
        return true;
    }

    return false;
}

//------------------------------------------------------------------------
// lvaIsOSRLocal: check if this local var is one that requires special
//     treatment for OSR compilations.
//
// Arguments:
//    varNum     - variable of interest
//
// Return Value:
//    true       - this is an OSR compile and this local requires special treatment
//    false      - not an OSR compile, or not an interesting local for OSR

bool Compiler::lvaIsOSRLocal(unsigned varNum)
{
    LclVarDsc* const varDsc = lvaGetDesc(varNum);

#ifdef DEBUG
    if (opts.IsOSR())
    {
        if (varDsc->lvIsOSRLocal)
        {
            // Sanity check for promoted fields of OSR locals.
            //
            if (varNum >= info.compLocalsCount)
            {
                assert(varDsc->lvIsStructField);
                assert(varDsc->lvParentLcl < info.compLocalsCount);
            }
        }
    }
    else
    {
        assert(!varDsc->lvIsOSRLocal);
    }
#endif

    return varDsc->lvIsOSRLocal;
}

//------------------------------------------------------------------------------
// gtTypeForNullCheck: helper to get the most optimal and correct type for nullcheck
//
// Arguments:
//    tree - the node for nullcheck;
//
var_types Compiler::gtTypeForNullCheck(GenTree* tree)
{
    static const var_types s_typesBySize[] = {TYP_UNDEF, TYP_BYTE,  TYP_SHORT, TYP_UNDEF, TYP_INT,
                                              TYP_UNDEF, TYP_UNDEF, TYP_UNDEF, TYP_LONG};

    if (!varTypeIsStruct(tree))
    {
#if defined(TARGET_XARCH)
        // Just an optimization for XARCH - smaller mov
        if (genTypeSize(tree) == 8)
        {
            return TYP_INT;
        }
#endif

        assert((genTypeSize(tree) < ARRAY_SIZE(s_typesBySize)) && (s_typesBySize[genTypeSize(tree)] != TYP_UNDEF));
        return s_typesBySize[genTypeSize(tree)];
    }
    // for the rest: probe a single byte to avoid potential AVEs
    return TYP_BYTE;
}

//------------------------------------------------------------------------------
// gtChangeOperToNullCheck: helper to change tree oper to a NULLCHECK.
//
// Arguments:
//    tree  - the node to change;
//
// Notes:
//    the function should not be called after lowering for platforms that do not support
//    emitting NULLCHECK nodes, like arm32. Use `Lowering::TransformUnusedIndirection`
//    that handles it and calls this function when appropriate.
//
void Compiler::gtChangeOperToNullCheck(GenTree* tree)
{
    assert(tree->OperIs(GT_IND, GT_BLK));
    tree->ChangeOper(GT_NULLCHECK);
    tree->ChangeType(gtTypeForNullCheck(tree));
    tree->SetIndirExceptionFlags(this);
    optMethodFlags |= OMF_HAS_NULLCHECK;
}

#if defined(DEBUG)
//------------------------------------------------------------------------------
// devirtualizationDetailToString: describe the detailed devirtualization reason
//
// Arguments:
//    detail - detail to describe
//
// Returns:
//    descriptive string
//
const char* Compiler::devirtualizationDetailToString(CORINFO_DEVIRTUALIZATION_DETAIL detail)
{
    switch (detail)
    {
        case CORINFO_DEVIRTUALIZATION_UNKNOWN:
            return "unknown";
        case CORINFO_DEVIRTUALIZATION_SUCCESS:
            return "success";
        case CORINFO_DEVIRTUALIZATION_FAILED_CANON:
            return "object class was canonical";
        case CORINFO_DEVIRTUALIZATION_FAILED_COM:
            return "object class was com";
        case CORINFO_DEVIRTUALIZATION_FAILED_CAST:
            return "object class could not be cast to interface class";
        case CORINFO_DEVIRTUALIZATION_FAILED_LOOKUP:
            return "interface method could not be found";
        case CORINFO_DEVIRTUALIZATION_FAILED_DIM:
            return "interface method was default interface method";
        case CORINFO_DEVIRTUALIZATION_FAILED_SUBCLASS:
            return "object not subclass of base class";
        case CORINFO_DEVIRTUALIZATION_FAILED_SLOT:
            return "virtual method installed via explicit override";
        case CORINFO_DEVIRTUALIZATION_FAILED_BUBBLE:
            return "devirtualization crossed version bubble";
        case CORINFO_DEVIRTUALIZATION_MULTIPLE_IMPL:
            return "object class has multiple implementations of interface";
        case CORINFO_DEVIRTUALIZATION_FAILED_BUBBLE_CLASS_DECL:
            return "decl method is defined on class and decl method not in version bubble, and decl method not in "
                   "type closest to version bubble";
        case CORINFO_DEVIRTUALIZATION_FAILED_BUBBLE_INTERFACE_DECL:
            return "decl method is defined on interface and not in version bubble, and implementation type not "
                   "entirely defined in bubble";
        case CORINFO_DEVIRTUALIZATION_FAILED_BUBBLE_IMPL:
            return "object class not defined within version bubble";
        case CORINFO_DEVIRTUALIZATION_FAILED_BUBBLE_IMPL_NOT_REFERENCEABLE:
            return "object class cannot be referenced from R2R code due to missing tokens";
        case CORINFO_DEVIRTUALIZATION_FAILED_DUPLICATE_INTERFACE:
            return "crossgen2 virtual method algorithm and runtime algorithm differ in the presence of duplicate "
                   "interface implementations";
        case CORINFO_DEVIRTUALIZATION_FAILED_DECL_NOT_REPRESENTABLE:
            return "Decl method cannot be represented in R2R image";
        case CORINFO_DEVIRTUALIZATION_FAILED_TYPE_EQUIVALENCE:
            return "Support for type equivalence in devirtualization is not yet implemented in crossgen2";
        default:
            return "undefined";
    }
}

//------------------------------------------------------------------------------
// printfAlloc: printf a string and allocate the result in CMK_DebugOnly
// memory.
//
// Arguments:
//    format - Format string
//
// Returns:
//    Allocated string.
//
const char* Compiler::printfAlloc(const char* format, ...)
{
    va_list args;
    va_start(args, format);
    int count = _vscprintf(format, args);
    va_end(args);

    assert(count >= 0);
    char* resultStr = new (this, CMK_DebugOnly) char[count + 1];

    va_start(args, format);
    int result = vsprintf_s(resultStr, count + 1, format, args);
    va_end(args);

    assert((result >= 0) && (result < (count + 1)));
    return resultStr;
}

//------------------------------------------------------------------------
// convertUtf16ToUtf8ForPrinting:
//   Convert a string from UTF16 to UTF8 to be printed to output.
//
// Arguments:
//    utf16Src     - source string in UTF16 encoding
//    utf16SrcLen  - length of the source string in UTF16 encoding
//    utf8Dst      - destination buffer for the UTF8 string
//    utf8DstLen   - length of the destination buffer in bytes
//
// Notes:
//     "<string is too long>" is written to the destination buffer if the
//     converted string exceeds the buffer size.
//
void Compiler::convertUtf16ToUtf8ForPrinting(const char16_t* utf16Src,
                                             size_t          utf16SrcLen,
                                             char*           utf8Dst,
                                             size_t          utf8DstLen)
{
    const CHAR16_T* utf16src      = reinterpret_cast<const CHAR16_T*>(utf16Src);
    size_t          actualUtf8Len = minipal_get_length_utf16_to_utf8(utf16src, utf16SrcLen, 0);
    if (actualUtf8Len >= utf8DstLen)
    {
        strcpy_s(utf8Dst, utf8DstLen, "<string is too long>");
        return;
    }
    size_t written = minipal_convert_utf16_to_utf8(utf16src, utf16SrcLen, utf8Dst, utf8DstLen, 0);
    assert(written < utf8DstLen);
    utf8Dst[written] = '\0';
}

#endif // defined(DEBUG)

#if TRACK_ENREG_STATS
Compiler::EnregisterStats Compiler::s_enregisterStats;

void Compiler::EnregisterStats::RecordLocal(const LclVarDsc* varDsc)
{
    m_totalNumberOfVars++;
    if (varDsc->TypeIs(TYP_STRUCT))
    {
        m_totalNumberOfStructVars++;
    }
    if (!varDsc->lvDoNotEnregister)
    {
        m_totalNumberOfEnregVars++;
        if (varDsc->TypeIs(TYP_STRUCT))
        {
            m_totalNumberOfStructEnregVars++;
        }
    }
    else
    {
        switch (varDsc->GetDoNotEnregReason())
        {
            case DoNotEnregisterReason::AddrExposed:
                m_addrExposed++;
                break;
            case DoNotEnregisterReason::HiddenBufferStructArg:
                m_hiddenStructArg++;
                break;
            case DoNotEnregisterReason::DontEnregStructs:
                m_dontEnregStructs++;
                break;
            case DoNotEnregisterReason::NotRegSizeStruct:
                m_notRegSizeStruct++;
                break;
            case DoNotEnregisterReason::LocalField:
                m_localField++;
                break;
            case DoNotEnregisterReason::VMNeedsStackAddr:
                m_VMNeedsStackAddr++;
                break;
            case DoNotEnregisterReason::LiveInOutOfHandler:
                m_liveInOutHndlr++;
                break;
            case DoNotEnregisterReason::BlockOp:
                m_blockOp++;
                break;
            case DoNotEnregisterReason::IsStructArg:
                m_structArg++;
                break;
            case DoNotEnregisterReason::DepField:
                m_depField++;
                break;
            case DoNotEnregisterReason::NoRegVars:
                m_noRegVars++;
                break;
#if !defined(TARGET_64BIT)
            case DoNotEnregisterReason::LongParamField:
                m_longParamField++;
                break;
#endif
#ifdef JIT32_GCENCODER
            case DoNotEnregisterReason::PinningRef:
                m_PinningRef++;
                break;
#endif
            case DoNotEnregisterReason::LclAddrNode:
                m_lclAddrNode++;
                break;

            case DoNotEnregisterReason::CastTakesAddr:
                m_castTakesAddr++;
                break;

            case DoNotEnregisterReason::StoreBlkSrc:
                m_storeBlkSrc++;
                break;

            case DoNotEnregisterReason::SwizzleArg:
                m_swizzleArg++;
                break;

            case DoNotEnregisterReason::BlockOpRet:
                m_blockOpRet++;
                break;

            case DoNotEnregisterReason::ReturnSpCheck:
                m_returnSpCheck++;
                break;

            case DoNotEnregisterReason::CallSpCheck:
                m_callSpCheck++;
                break;

            case DoNotEnregisterReason::SimdUserForcesDep:
                m_simdUserForcesDep++;
                break;

            default:
                unreached();
                break;
        }

        if (varDsc->GetDoNotEnregReason() == DoNotEnregisterReason::AddrExposed)
        {
            // We can't `assert(IsAddressExposed())` because `fgAdjustForAddressExposedOrWrittenThis`
            // does not clear `m_doNotEnregReason` on `this`.
            switch (varDsc->GetAddrExposedReason())
            {
                case AddressExposedReason::PARENT_EXPOSED:
                    m_parentExposed++;
                    break;

                case AddressExposedReason::TOO_CONSERVATIVE:
                    m_tooConservative++;
                    break;

                case AddressExposedReason::ESCAPE_ADDRESS:
                    m_escapeAddress++;
                    break;

                case AddressExposedReason::WIDE_INDIR:
                    m_wideIndir++;
                    break;

                case AddressExposedReason::OSR_EXPOSED:
                    m_osrExposed++;
                    break;

                case AddressExposedReason::STRESS_LCL_FLD:
                    m_stressLclFld++;
                    break;

                case AddressExposedReason::DISPATCH_RET_BUF:
                    m_dispatchRetBuf++;
                    break;

                case AddressExposedReason::STRESS_POISON_IMPLICIT_BYREFS:
                    m_stressPoisonImplicitByrefs++;
                    break;

                case AddressExposedReason::EXTERNALLY_VISIBLE_IMPLICITLY:
                    m_externallyVisibleImplicitly++;
                    break;

                default:
                    unreached();
                    break;
            }
        }
    }
}

void Compiler::EnregisterStats::Dump(FILE* fout) const
{
    const unsigned totalNumberOfNotStructVars =
        s_enregisterStats.m_totalNumberOfVars - s_enregisterStats.m_totalNumberOfStructVars;
    const unsigned totalNumberOfNotStructEnregVars =
        s_enregisterStats.m_totalNumberOfEnregVars - s_enregisterStats.m_totalNumberOfStructEnregVars;
    const unsigned notEnreg = s_enregisterStats.m_totalNumberOfVars - s_enregisterStats.m_totalNumberOfEnregVars;

    fprintf(fout, "\nLocals enregistration statistics:\n");
    if (m_totalNumberOfVars == 0)
    {
        fprintf(fout, "No locals to report.\n");
        return;
    }
    fprintf(fout, "total number of locals: %d, number of enregistered: %d, notEnreg: %d, ratio: %.2f\n",
            m_totalNumberOfVars, m_totalNumberOfEnregVars, m_totalNumberOfVars - m_totalNumberOfEnregVars,
            (float)m_totalNumberOfEnregVars / m_totalNumberOfVars);

    if (m_totalNumberOfStructVars != 0)
    {
        fprintf(fout, "total number of struct locals: %d, number of enregistered: %d, notEnreg: %d, ratio: %.2f\n",
                m_totalNumberOfStructVars, m_totalNumberOfStructEnregVars,
                m_totalNumberOfStructVars - m_totalNumberOfStructEnregVars,
                (float)m_totalNumberOfStructEnregVars / m_totalNumberOfStructVars);
    }

    const unsigned numberOfPrimitiveLocals = totalNumberOfNotStructVars - totalNumberOfNotStructEnregVars;
    if (numberOfPrimitiveLocals != 0)
    {
        fprintf(fout, "total number of primitive locals: %d, number of enregistered: %d, notEnreg: %d, ratio: %.2f\n",
                totalNumberOfNotStructVars, totalNumberOfNotStructEnregVars, numberOfPrimitiveLocals,
                (float)totalNumberOfNotStructEnregVars / totalNumberOfNotStructVars);
    }

    if (notEnreg == 0)
    {
        fprintf(fout, "All locals are enregistered.\n");
        return;
    }

#define PRINT_STATS(stat, total)                                                                                       \
    if (stat != 0)                                                                                                     \
    {                                                                                                                  \
        fprintf(fout, #stat " %d, ratio: %.2f\n", stat, (float)stat / total);                                          \
    }

    PRINT_STATS(m_addrExposed, notEnreg);
    PRINT_STATS(m_hiddenStructArg, notEnreg);
    PRINT_STATS(m_dontEnregStructs, notEnreg);
    PRINT_STATS(m_notRegSizeStruct, notEnreg);
    PRINT_STATS(m_localField, notEnreg);
    PRINT_STATS(m_VMNeedsStackAddr, notEnreg);
    PRINT_STATS(m_liveInOutHndlr, notEnreg);
    PRINT_STATS(m_blockOp, notEnreg);
    PRINT_STATS(m_structArg, notEnreg);
    PRINT_STATS(m_depField, notEnreg);
    PRINT_STATS(m_noRegVars, notEnreg);
#if !defined(TARGET_64BIT)
    PRINT_STATS(m_longParamField, notEnreg);
#endif // !TARGET_64BIT
#ifdef JIT32_GCENCODER
    PRINT_STATS(m_PinningRef, notEnreg);
#endif // JIT32_GCENCODER
    PRINT_STATS(m_lclAddrNode, notEnreg);
    PRINT_STATS(m_castTakesAddr, notEnreg);
    PRINT_STATS(m_storeBlkSrc, notEnreg);
    PRINT_STATS(m_swizzleArg, notEnreg);
    PRINT_STATS(m_blockOpRet, notEnreg);
    PRINT_STATS(m_returnSpCheck, notEnreg);
    PRINT_STATS(m_callSpCheck, notEnreg);
    PRINT_STATS(m_simdUserForcesDep, notEnreg);

    fprintf(fout, "\nAddr exposed details:\n");
    if (m_addrExposed == 0)
    {
        fprintf(fout, "\nNo address exposed locals to report.\n");
        return;
    }

    PRINT_STATS(m_parentExposed, m_addrExposed);
    PRINT_STATS(m_tooConservative, m_addrExposed);
    PRINT_STATS(m_escapeAddress, m_addrExposed);
    PRINT_STATS(m_wideIndir, m_addrExposed);
    PRINT_STATS(m_osrExposed, m_addrExposed);
    PRINT_STATS(m_stressLclFld, m_addrExposed);
    PRINT_STATS(m_dispatchRetBuf, m_addrExposed);
    PRINT_STATS(m_stressPoisonImplicitByrefs, m_addrExposed);
    PRINT_STATS(m_externallyVisibleImplicitly, m_addrExposed);
}
#endif // TRACK_ENREG_STATS
